Files
five/hbrt/shutdown.go
Charles KWON OhJun d7513eeb24 fix: Code review round 2 — race conditions, dead code, hardcoded paths
CRITICAL fixes:
- #1 vm.go: Mutex on libModules/dynamicFuncs global slices
  RegisterLibModule/RegisterDynamicFunc now thread-safe
  RegisterLibModules copies under lock, clears, releases
- #4 shutdown.go: Signal handler goroutine leak fixed
  Uses done channel + select for clean exit on normal shutdown

HIGH fixes:
- #7-8 gobridge.go: Remove dead if/else branches (both identical)
- #13-14 main.go: Remove hardcoded /mnt/d/harbour-core paths
  Use HB_INC env var + standard /usr/local/include/harbour only
- #15 main.go: Remove unused frbModSeq variable

MEDIUM fixes:
- #22 expr.go: Remove unused parts variable in parseInterpolatedString
- #51 macro.go: Remove var _ = fmt.Sprintf import guard
- macroeval.go: Remove unused lexer import and guard

Total fixed this session: 12/53 issues resolved
Remaining: 41 (CRITICAL: 1, HIGH: 9, MEDIUM: 16, LOW: 16)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:32:09 +09:00

261 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 ---
// signalDone closes to stop the signal handler goroutine on normal exit.
var signalDone chan struct{}
// InstallSignalHandlers sets up OS signal handlers for clean shutdown.
// Harbour: hb_vmSetExceptionHandler (SIGINT, SIGTERM, SIGSEGV)
func (vm *VM) InstallSignalHandlers() {
signalSetup.Do(func() {
sigCh := make(chan os.Signal, 1)
signalDone = make(chan struct{})
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
select {
case sig := <-sigCh:
fmt.Fprintf(os.Stderr, "\nFive: received %v, shutting down...\n", sig)
vm.Shutdown()
os.Exit(1)
case <-signalDone:
// Normal exit — stop goroutine cleanly
signal.Stop(sigCh)
return
}
}()
})
}
// stopSignalHandler stops the signal handler goroutine (called during normal shutdown).
func stopSignalHandler() {
if signalDone != nil {
select {
case <-signalDone:
// already closed
default:
close(signalDone)
}
}
}
// --- 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
// Phase 10: Stop signal handler goroutine (prevent leak)
stopSignalHandler()
}
// runExitProcedures executes all EXIT PROCEDURE declarations.
// Harbour: scans symbols for HB_FS_EXIT scope, executes in reverse module order.
func (vm *VM) runExitProcedures() {
// Collect EXIT procedures under lock, execute without lock
vm.mu.RLock()
var exitFuncs []func(*Thread)
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 {
exitFuncs = append(exitFuncs, sym.Func)
}
}
}
vm.mu.RUnlock()
for _, fn := range exitFuncs {
safeCallThread(vm, fn)
}
}
// closeAllWorkAreas closes all open database work areas.
// Harbour: hb_rddCloseAll() — respects parent-child order.
func (vm *VM) closeAllWorkAreas() {
vm.mu.RLock()
threads := make([]*Thread, len(vm.threads))
copy(threads, vm.threads)
vm.mu.RUnlock()
for _, t := range 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)
}