CRITICAL fixes: - fileio.go: Add sync.Mutex to file handle table (race condition #2) allocHandle/getHandle/removeHandle thread-safe helpers - goroutine.go: Add defer/recover to GoLaunch/GoLaunchBlock Goroutine panic no longer crashes entire process (#5) HIGH fixes: - strings.go: Implement proper LTrim (TrimLeft) and RTrim (TrimRight) Previously both aliased to AllTrim — silent semantic bug (#18) - register.go: TRIM = RTrim (Harbour compatible) From 53-issue senior code review. Remaining: 47 issues (HIGH: 10, MEDIUM: 18, LOW: 16, CRITICAL: 3) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
149 lines
2.9 KiB
Go
149 lines
2.9 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// Goroutine support for Five runtime.
|
|
// Provides Go's goroutine, channel, and WaitGroup primitives
|
|
// as first-class Harbour values.
|
|
|
|
package hbrt
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
// --- Channel ---
|
|
|
|
// HbChannel wraps Go's chan Value for use in PRG code.
|
|
type HbChannel struct {
|
|
Ch chan Value
|
|
}
|
|
|
|
// MakeChannel creates a channel Value with optional buffer size.
|
|
func MakeChannel(size int) Value {
|
|
return MakePointer(&HbChannel{Ch: make(chan Value, size)})
|
|
}
|
|
|
|
// AsChannel extracts HbChannel from a Pointer value.
|
|
func (v Value) AsChannel() *HbChannel {
|
|
if !v.IsPointer() {
|
|
return nil
|
|
}
|
|
if ch, ok := v.AsPointer().(*HbChannel); ok {
|
|
return ch
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Send sends a value into the channel.
|
|
func (ch *HbChannel) Send(val Value) {
|
|
ch.Ch <- val
|
|
}
|
|
|
|
// Receive receives a value from the channel.
|
|
func (ch *HbChannel) Receive() Value {
|
|
return <-ch.Ch
|
|
}
|
|
|
|
// TryReceive attempts non-blocking receive. Returns (value, true) or (nil, false).
|
|
func (ch *HbChannel) TryReceive() (Value, bool) {
|
|
select {
|
|
case v := <-ch.Ch:
|
|
return v, true
|
|
default:
|
|
return MakeNil(), false
|
|
}
|
|
}
|
|
|
|
// Close closes the channel.
|
|
func (ch *HbChannel) Close() {
|
|
close(ch.Ch)
|
|
}
|
|
|
|
// --- WaitGroup ---
|
|
|
|
// HbWaitGroup wraps sync.WaitGroup.
|
|
type HbWaitGroup struct {
|
|
WG sync.WaitGroup
|
|
}
|
|
|
|
// MakeWaitGroup creates a WaitGroup Value with initial count.
|
|
func MakeWaitGroup(n int) Value {
|
|
wg := &HbWaitGroup{}
|
|
if n > 0 {
|
|
wg.WG.Add(n)
|
|
}
|
|
return MakePointer(wg)
|
|
}
|
|
|
|
// AsWaitGroup extracts HbWaitGroup from a Pointer value.
|
|
func (v Value) AsWaitGroup() *HbWaitGroup {
|
|
if !v.IsPointer() {
|
|
return nil
|
|
}
|
|
if wg, ok := v.AsPointer().(*HbWaitGroup); ok {
|
|
return wg
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// --- Mutex ---
|
|
|
|
// HbMutex wraps sync.Mutex.
|
|
type HbMutex struct {
|
|
Mu sync.Mutex
|
|
}
|
|
|
|
// MakeMutex creates a Mutex Value.
|
|
func MakeMutex() Value {
|
|
return MakePointer(&HbMutex{})
|
|
}
|
|
|
|
// AsMutex extracts HbMutex from a Pointer value.
|
|
func (v Value) AsMutex() *HbMutex {
|
|
if !v.IsPointer() {
|
|
return nil
|
|
}
|
|
if mu, ok := v.AsPointer().(*HbMutex); ok {
|
|
return mu
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// --- GoRoutine launcher ---
|
|
|
|
// GoLaunch spawns a new goroutine that runs a function on a new Thread.
|
|
func (vm *VM) GoLaunch(fn func(*Thread), args []Value) {
|
|
go func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
fmt.Fprintf(os.Stderr, "Five goroutine panic: %v\n", r)
|
|
}
|
|
}()
|
|
t := vm.NewThread()
|
|
for _, a := range args {
|
|
t.push(a)
|
|
}
|
|
t.PendingParams2(len(args))
|
|
fn(t)
|
|
}()
|
|
}
|
|
|
|
// GoLaunchBlock spawns a goroutine that evaluates a code block.
|
|
func (vm *VM) GoLaunchBlock(blk *HbBlock, args []Value) {
|
|
go func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
fmt.Fprintf(os.Stderr, "Five goroutine panic: %v\n", r)
|
|
}
|
|
}()
|
|
t := vm.NewThread()
|
|
for _, a := range args {
|
|
t.push(a)
|
|
}
|
|
t.PendingParams2(len(args))
|
|
blk.Fn(t)
|
|
}()
|
|
}
|