Files
five/hbrt/goroutine.go
Charles KWON OhJun 207fa9f7dd fix: Phase 1 Step 0 cleanup + CRITICAL #3, MEDIUM #36-37, LOW #50
Files modified (5):
  hbrt/macro.go — Replace hand-rolled parseFloat/parseInt64 with strconv (#50)
                   Remove stale TODO, redundant TrimSpace
  hbrt/macroeval.go — Use strconv for literal parsing (was using removed functions)
  hbrt/class.go — CRITICAL #3: Change RWMutex to Mutex on classList
                   Prevents slice reallocation race on concurrent GetClass
  hbrt/goroutine.go — #36: Channel double-close protection (sync.Once)
                       #37: Send on closed channel recovery (defer/recover)
                       Add IsClosed(), safe Receive (handles closed channel)
  hbrt/gobridge.go — Already clean (confirmed)
  hbrt/hbfunc.go — Already clean (confirmed)

Issues resolved: #3 (CRITICAL), #36, #37 (MEDIUM), #50 (LOW)
Total fixed: 16/53

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

171 lines
3.3 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
closeOnce sync.Once
closed bool
}
// 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. Safe if channel is closed (recovers panic).
func (ch *HbChannel) Send(val Value) {
defer func() {
if r := recover(); r != nil {
// send on closed channel — silently ignore
}
}()
ch.Ch <- val
}
// Receive receives a value from the channel.
func (ch *HbChannel) Receive() Value {
v, ok := <-ch.Ch
if !ok {
return MakeNil() // channel closed
}
return v
}
// TryReceive attempts non-blocking receive. Returns (value, true) or (nil, false).
func (ch *HbChannel) TryReceive() (Value, bool) {
select {
case v, ok := <-ch.Ch:
if !ok {
return MakeNil(), false
}
return v, true
default:
return MakeNil(), false
}
}
// Close closes the channel. Safe to call multiple times.
func (ch *HbChannel) Close() {
ch.closeOnce.Do(func() {
ch.closed = true
close(ch.Ch)
})
}
// IsClosed returns true if the channel has been closed.
func (ch *HbChannel) IsClosed() bool {
return ch.closed
}
// --- 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)
}()
}