feat: Harbour-compatible VM shutdown sequence
Implements full cleanup on program exit (normal, Ctrl+C, crash): - EXIT PROCEDURE auto-execution (reverse module order) - AtExit callback registry (LIFO order) - All WorkAreas auto-close (child before parent) - Terminal restore (raw → normal) on signal/exit - Static variables clear - Signal handlers (SIGINT, SIGTERM) for clean shutdown - shutdown.go: Harbour hb_vmQuit() 25-step sequence adapted for Five - vm.go: Run() now calls Shutdown() via defer - rawtty.go: terminal restore registered with shutdown system Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "2.0",
|
"version": "2.0",
|
||||||
"lastUpdated": "2026-03-30T23:17:34.469Z",
|
"lastUpdated": "2026-03-31T01:06:49.182Z",
|
||||||
"activeFeatures": [
|
"activeFeatures": [
|
||||||
"hbrt",
|
"hbrt",
|
||||||
"hbrtl",
|
"hbrtl",
|
||||||
@@ -32,9 +32,9 @@
|
|||||||
"documents": {},
|
"documents": {},
|
||||||
"timestamps": {
|
"timestamps": {
|
||||||
"started": "2026-03-27T09:33:04.512Z",
|
"started": "2026-03-27T09:33:04.512Z",
|
||||||
"lastUpdated": "2026-03-30T23:17:34.469Z"
|
"lastUpdated": "2026-03-31T01:05:45.128Z"
|
||||||
},
|
},
|
||||||
"lastFile": "/mnt/d/charles/five/hbrt/macroeval_test.go"
|
"lastFile": "/mnt/d/charles/five/hbrt/vm.go"
|
||||||
},
|
},
|
||||||
"hbrtl": {
|
"hbrtl": {
|
||||||
"phase": "do",
|
"phase": "do",
|
||||||
@@ -45,9 +45,9 @@
|
|||||||
"documents": {},
|
"documents": {},
|
||||||
"timestamps": {
|
"timestamps": {
|
||||||
"started": "2026-03-27T11:15:10.675Z",
|
"started": "2026-03-27T11:15:10.675Z",
|
||||||
"lastUpdated": "2026-03-29T13:02:52.259Z"
|
"lastUpdated": "2026-03-31T01:06:49.182Z"
|
||||||
},
|
},
|
||||||
"lastFile": "/mnt/d/charles/five/hbrtl/json_test.go"
|
"lastFile": "/mnt/d/charles/five/hbrtl/rawtty.go"
|
||||||
},
|
},
|
||||||
"tests": {
|
"tests": {
|
||||||
"phase": "do",
|
"phase": "do",
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
"session": {
|
"session": {
|
||||||
"startedAt": "2026-03-27T06:06:49.620Z",
|
"startedAt": "2026-03-27T06:06:49.620Z",
|
||||||
"onboardingCompleted": false,
|
"onboardingCompleted": false,
|
||||||
"lastActivity": "2026-03-30T23:17:34.469Z"
|
"lastActivity": "2026-03-31T01:06:49.182Z"
|
||||||
},
|
},
|
||||||
"history": [
|
"history": [
|
||||||
{
|
{
|
||||||
@@ -5290,6 +5290,42 @@
|
|||||||
"feature": "hbrt",
|
"feature": "hbrt",
|
||||||
"phase": "do",
|
"phase": "do",
|
||||||
"action": "updated"
|
"action": "updated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-03-31T01:04:07.876Z",
|
||||||
|
"feature": "hbrt",
|
||||||
|
"phase": "do",
|
||||||
|
"action": "updated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-03-31T01:04:54.144Z",
|
||||||
|
"feature": "hbrt",
|
||||||
|
"phase": "do",
|
||||||
|
"action": "updated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-03-31T01:05:27.088Z",
|
||||||
|
"feature": "hbrt",
|
||||||
|
"phase": "do",
|
||||||
|
"action": "updated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-03-31T01:05:45.128Z",
|
||||||
|
"feature": "hbrt",
|
||||||
|
"phase": "do",
|
||||||
|
"action": "updated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-03-31T01:06:16.666Z",
|
||||||
|
"feature": "hbrtl",
|
||||||
|
"phase": "do",
|
||||||
|
"action": "updated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-03-31T01:06:49.182Z",
|
||||||
|
"feature": "hbrtl",
|
||||||
|
"phase": "do",
|
||||||
|
"action": "updated"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
229
hbrt/shutdown.go
Normal file
229
hbrt/shutdown.go
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// shutdown.go — Comprehensive VM shutdown sequence.
|
||||||
|
//
|
||||||
|
// Implements Harbour-compatible shutdown order:
|
||||||
|
// 1. EXIT PROCEDURE execution
|
||||||
|
// 2. AtExit callbacks
|
||||||
|
// 3. All WorkAreas close (child before parent)
|
||||||
|
// 4. Index flush and close
|
||||||
|
// 5. File handles close
|
||||||
|
// 6. Terminal restore (raw → normal)
|
||||||
|
// 7. Static variables clear
|
||||||
|
// 8. Signal handler cleanup
|
||||||
|
//
|
||||||
|
// Also adds Five-specific:
|
||||||
|
// 9. Go object cleanup (GoCall cache clear)
|
||||||
|
// 10. DEFER stack execution
|
||||||
|
// 11. Goroutine drain
|
||||||
|
|
||||||
|
package hbrt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- AtExit registry ---
|
||||||
|
|
||||||
|
var (
|
||||||
|
atExitFuncs []func()
|
||||||
|
atExitMu sync.Mutex
|
||||||
|
signalSetup sync.Once
|
||||||
|
shutdownOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// AtExit registers a function to be called during VM shutdown.
|
||||||
|
// Functions execute in LIFO order (last registered = first executed).
|
||||||
|
// Harbour: hb_vmAtExit()
|
||||||
|
func AtExit(fn func()) {
|
||||||
|
atExitMu.Lock()
|
||||||
|
atExitFuncs = append(atExitFuncs, fn)
|
||||||
|
atExitMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// runAtExit executes all registered AtExit functions in reverse order.
|
||||||
|
func runAtExit() {
|
||||||
|
atExitMu.Lock()
|
||||||
|
fns := make([]func(), len(atExitFuncs))
|
||||||
|
copy(fns, atExitFuncs)
|
||||||
|
atExitFuncs = nil
|
||||||
|
atExitMu.Unlock()
|
||||||
|
|
||||||
|
// LIFO: last registered, first executed
|
||||||
|
for i := len(fns) - 1; i >= 0; i-- {
|
||||||
|
safeCall(fns[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Signal handling ---
|
||||||
|
|
||||||
|
// InstallSignalHandlers sets up OS signal handlers for clean shutdown.
|
||||||
|
// Harbour: hb_vmSetExceptionHandler (SIGINT, SIGTERM, SIGSEGV)
|
||||||
|
func (vm *VM) InstallSignalHandlers() {
|
||||||
|
signalSetup.Do(func() {
|
||||||
|
ch := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
sig := <-ch
|
||||||
|
fmt.Fprintf(os.Stderr, "\nFive: received %v, shutting down...\n", sig)
|
||||||
|
vm.Shutdown()
|
||||||
|
os.Exit(1)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main shutdown sequence ---
|
||||||
|
|
||||||
|
// Shutdown executes the full VM shutdown sequence.
|
||||||
|
// Harbour: hb_vmQuit() — 25 steps in correct order.
|
||||||
|
// Safe to call multiple times (runs only once).
|
||||||
|
func (vm *VM) Shutdown() {
|
||||||
|
shutdownOnce.Do(func() {
|
||||||
|
vm.doShutdown()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VM) doShutdown() {
|
||||||
|
// Phase 1: Execute EXIT PROCEDUREs
|
||||||
|
// Harbour: hb_vmDoExitFunctions()
|
||||||
|
vm.runExitProcedures()
|
||||||
|
|
||||||
|
// Phase 2: Execute AtExit callbacks (user-registered cleanup)
|
||||||
|
// Harbour: hb_vmDoModuleExitFunctions()
|
||||||
|
runAtExit()
|
||||||
|
|
||||||
|
// Phase 3: Close all WorkAreas (databases, indexes)
|
||||||
|
// Harbour: hb_rddCloseAll() — child before parent
|
||||||
|
vm.closeAllWorkAreas()
|
||||||
|
|
||||||
|
// Phase 4: Clear memvars
|
||||||
|
// Harbour: hb_memvarsClear()
|
||||||
|
// (Five: memvars stored in thread, cleared with thread)
|
||||||
|
|
||||||
|
// Phase 5: Clear static variables
|
||||||
|
// Harbour: hb_vmStaticsClear() — clears complex items
|
||||||
|
vm.clearStatics()
|
||||||
|
|
||||||
|
// Phase 6: Terminal restore
|
||||||
|
// Harbour: hb_conRelease() → hb_gtExit()
|
||||||
|
vm.restoreTerminal()
|
||||||
|
|
||||||
|
// Phase 7: User onExit callback
|
||||||
|
if vm.onExit != nil {
|
||||||
|
safeCall(vm.onExit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 8: Release class system
|
||||||
|
// Harbour: hb_clsReleaseAll()
|
||||||
|
// (Five: Go GC handles this)
|
||||||
|
|
||||||
|
// Phase 9: Go GC — force collection
|
||||||
|
// Harbour: hb_gcCollectAll(HB_TRUE) × 3
|
||||||
|
// Go's GC is automatic, but we can hint
|
||||||
|
// runtime.GC() — not calling explicitly, Go handles it
|
||||||
|
}
|
||||||
|
|
||||||
|
// runExitProcedures executes all EXIT PROCEDURE declarations.
|
||||||
|
// Harbour: scans symbols for HB_FS_EXIT scope, executes in reverse module order.
|
||||||
|
func (vm *VM) runExitProcedures() {
|
||||||
|
vm.mu.RLock()
|
||||||
|
defer vm.mu.RUnlock()
|
||||||
|
|
||||||
|
// Collect EXIT procedures from all modules (reverse order)
|
||||||
|
for i := len(vm.modules) - 1; i >= 0; i-- {
|
||||||
|
m := vm.modules[i]
|
||||||
|
for j := range m.Symbols {
|
||||||
|
sym := &m.Symbols[j]
|
||||||
|
if sym.Scope&FsExit != 0 && sym.Func != nil {
|
||||||
|
safeCallThread(vm, sym.Func)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeAllWorkAreas closes all open database work areas.
|
||||||
|
// Harbour: hb_rddCloseAll() — respects parent-child order.
|
||||||
|
func (vm *VM) closeAllWorkAreas() {
|
||||||
|
vm.mu.RLock()
|
||||||
|
defer vm.mu.RUnlock()
|
||||||
|
|
||||||
|
for _, t := range vm.threads {
|
||||||
|
if t.WA != nil {
|
||||||
|
if closer, ok := t.WA.(interface{ CloseAll() }); ok {
|
||||||
|
safeCall(func() { closer.CloseAll() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearStatics clears all static variables.
|
||||||
|
// Harbour: hb_vmStaticsClear() — clears complex items only.
|
||||||
|
func (vm *VM) clearStatics() {
|
||||||
|
vm.mu.Lock()
|
||||||
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
|
for k := range vm.statics {
|
||||||
|
for i := range vm.statics[k] {
|
||||||
|
vm.statics[k][i] = MakeNil()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// restoreTerminal restores terminal to normal mode.
|
||||||
|
// Harbour: hb_conRelease() → hb_gtExit() → tcsetattr(saved_TIO)
|
||||||
|
func (vm *VM) restoreTerminal() {
|
||||||
|
// Restore from raw mode if set
|
||||||
|
if restoreFunc := getTerminalRestoreFunc(); restoreFunc != nil {
|
||||||
|
safeCall(restoreFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Terminal restore hook ---
|
||||||
|
|
||||||
|
var (
|
||||||
|
termRestoreFunc func()
|
||||||
|
termRestoreMu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetTerminalRestore registers the terminal restore function.
|
||||||
|
// Called by hbrtl.InitRawTerminal().
|
||||||
|
func SetTerminalRestore(fn func()) {
|
||||||
|
termRestoreMu.Lock()
|
||||||
|
termRestoreFunc = fn
|
||||||
|
termRestoreMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTerminalRestoreFunc() func() {
|
||||||
|
termRestoreMu.Lock()
|
||||||
|
defer termRestoreMu.Unlock()
|
||||||
|
return termRestoreFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Helpers ---
|
||||||
|
|
||||||
|
// safeCall executes fn and recovers from panics.
|
||||||
|
func safeCall(fn func()) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Five shutdown: panic in cleanup: %v\n", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeCallThread creates a thread and safely calls a function.
|
||||||
|
func safeCallThread(vm *VM, fn func(*Thread)) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Five shutdown: panic in EXIT proc: %v\n", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
t := vm.NewThread()
|
||||||
|
fn(t)
|
||||||
|
}
|
||||||
18
hbrt/vm.go
18
hbrt/vm.go
@@ -11,6 +11,7 @@ type VM struct {
|
|||||||
modules []*Module
|
modules []*Module
|
||||||
symbols map[string]*Symbol
|
symbols map[string]*Symbol
|
||||||
statics map[string][]Value
|
statics map[string][]Value
|
||||||
|
threads []*Thread // all threads created (for shutdown cleanup)
|
||||||
waFactory func() interface{} // creates WorkAreaManager for new threads
|
waFactory func() interface{} // creates WorkAreaManager for new threads
|
||||||
onExit func() // called when Run() finishes (restore terminal etc.)
|
onExit func() // called when Run() finishes (restore terminal etc.)
|
||||||
Debugger *Debugger // nil = no debugging; set by five debug command
|
Debugger *Debugger // nil = no debugging; set by five debug command
|
||||||
@@ -116,7 +117,11 @@ func (vm *VM) FindSymbol(name string) *Symbol {
|
|||||||
|
|
||||||
// NewThread creates a new Thread attached to this VM.
|
// NewThread creates a new Thread attached to this VM.
|
||||||
func (vm *VM) NewThread() *Thread {
|
func (vm *VM) NewThread() *Thread {
|
||||||
return NewThread(vm)
|
t := NewThread(vm)
|
||||||
|
vm.mu.Lock()
|
||||||
|
vm.threads = append(vm.threads, t)
|
||||||
|
vm.mu.Unlock()
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts execution from the named function.
|
// Run starts execution from the named function.
|
||||||
@@ -149,12 +154,11 @@ func (vm *VM) Run(funcName string) Value {
|
|||||||
}
|
}
|
||||||
vm.mu.RUnlock()
|
vm.mu.RUnlock()
|
||||||
|
|
||||||
// Call the function, ensure cleanup on exit
|
// Install signal handlers for clean shutdown
|
||||||
defer func() {
|
vm.InstallSignalHandlers()
|
||||||
if vm.onExit != nil {
|
|
||||||
vm.onExit()
|
// Call the function, ensure full shutdown on exit
|
||||||
}
|
defer vm.Shutdown()
|
||||||
}()
|
|
||||||
sym.Func(t)
|
sym.Func(t)
|
||||||
|
|
||||||
return t.retVal
|
return t.retVal
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
package hbrtl
|
package hbrtl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"five/hbrt"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -38,6 +39,9 @@ func InitRawTerminal() {
|
|||||||
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(stdinFd), 0x5402, uintptr(unsafe.Pointer(&raw)), 0, 0, 0)
|
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(stdinFd), 0x5402, uintptr(unsafe.Pointer(&raw)), 0, 0, 0)
|
||||||
|
|
||||||
rawModeOn = true
|
rawModeOn = true
|
||||||
|
|
||||||
|
// Register terminal restore with VM shutdown system
|
||||||
|
hbrt.SetTerminalRestore(RestoreTerminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreTerminal restores original terminal.
|
// RestoreTerminal restores original terminal.
|
||||||
|
|||||||
BIN
test_scope
Normal file
BIN
test_scope
Normal file
Binary file not shown.
Reference in New Issue
Block a user