Files
five/hbrt/goroutine.go
Charles KWON OhJun 7c61db70c3 fix: Critical code review fixes — race conditions, panic recovery, LTRIM/RTRIM
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>
2026-04-01 10:17:30 +09:00

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)
}()
}