github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/runtime/thread.go (about)

     1  package runtime
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  	"unsafe"
     7  )
     8  
     9  // ThreadStatus is the type of a thread status
    10  type ThreadStatus uint
    11  
    12  // Available statuses for threads.
    13  const (
    14  	ThreadOK        ThreadStatus = 0 // Running thread
    15  	ThreadSuspended ThreadStatus = 1 // Thread has yielded and is waiting to be resumed
    16  	ThreadDead      ThreadStatus = 3 // Thread has finished and cannot be resumed
    17  )
    18  
    19  // The depth of GoFunction calls in one thread is limited by this number in
    20  // order to avoid irrecoverable Go stack overflows.
    21  const maxGoFunctionCallDepth = 1000
    22  
    23  // Data passed between Threads via their resume channel (Thread.resumeCh).
    24  //
    25  // Supported types for exception are ContextTerminationError (which means
    26  // execution has run out of resources) and threadClose (which means the thread
    27  // should be closed without resuming execution, new in Lua 5.4 via the
    28  // coroutine.close() function).  Other types will cause a panic.
    29  type valuesError struct {
    30  	args      []Value     // arguments ot yield or resume
    31  	err       error       // execution error
    32  	exception interface{} // used when the thread should be closed right away
    33  }
    34  
    35  // A Thread is a lua thread.
    36  //
    37  // The mutex guarantees that if status == ThreadRunning, then caller
    38  // is not nil.
    39  //
    40  type Thread struct {
    41  	*Runtime
    42  	mux         sync.Mutex
    43  	status      ThreadStatus
    44  	closeErr    error // Error that caused the thread to stop
    45  	currentCont Cont  // Currently running continuation
    46  	resumeCh    chan valuesError
    47  	caller      *Thread // Who resumed this thread
    48  
    49  	// Depth of GoFunction calls in the thread.  This should not exceed
    50  	// maxGoFunctionCallDepth.  The aim is to avoid Go stack overflows that
    51  	// cannot be recovered from (note that this does not limit recursion for Lua
    52  	// functions).
    53  	goFunctionCallDepth int
    54  
    55  	DebugHooks
    56  
    57  	closeStack // Stack of pending to-be-closed values
    58  }
    59  
    60  // NewThread creates a new thread out of a Runtime.  Its initial
    61  // status is suspended.  Call Resume to run it.
    62  func NewThread(r *Runtime) *Thread {
    63  	r.RequireSize(unsafe.Sizeof(Thread{}) + 100) // 100 is my guess at the size of a channel
    64  	return &Thread{
    65  		resumeCh: make(chan valuesError),
    66  		status:   ThreadSuspended,
    67  		Runtime:  r,
    68  	}
    69  }
    70  
    71  // CurrentCont returns the continuation currently running (or suspended) in the
    72  // thread.
    73  func (t *Thread) CurrentCont() Cont {
    74  	return t.currentCont
    75  }
    76  
    77  // IsMain returns true if the thread is the runtime's main thread.
    78  func (t *Thread) IsMain() bool {
    79  	return t == t.mainThread
    80  }
    81  
    82  const maxErrorsInMessageHandler = 10
    83  
    84  var errErrorInMessageHandler = StringValue("error in error handling")
    85  
    86  // RunContinuation runs the continuation c in the thread. It keeps running until
    87  // the next continuation is nil or an error occurs, in which case it returns the
    88  // error.
    89  func (t *Thread) RunContinuation(c Cont) (err error) {
    90  	var next Cont
    91  	var errContCount = 0
    92  	_ = t.triggerCall(t, c)
    93  	for c != nil {
    94  		if t != t.gcThread {
    95  			t.runPendingFinalizers()
    96  		}
    97  		t.currentCont = c
    98  		next, err = c.RunInThread(t)
    99  		if err != nil {
   100  			rtErr := ToError(err)
   101  			if rtErr.Handled() {
   102  				return rtErr
   103  			}
   104  			err = rtErr.AddContext(c, -1)
   105  			errContCount++
   106  			if t.messageHandler != nil {
   107  				if errContCount > maxErrorsInMessageHandler {
   108  					return newHandledError(errErrorInMessageHandler)
   109  				}
   110  				next = t.messageHandler.Continuation(t, newMessageHandlerCont(c))
   111  			} else {
   112  				next = newMessageHandlerCont(c)
   113  			}
   114  			next.Push(t.Runtime, ErrorValue(err))
   115  		}
   116  		c = next
   117  	}
   118  	return
   119  }
   120  
   121  // This is to be able to close a suspended coroutine without completing it, but
   122  // still allow cleaning up the to-be-closed variables.  If this is put on the
   123  // resume channel of a running thread, yield will cause a panic in the goroutine
   124  // and that will be caught in the defer() clause below.
   125  type threadClose struct{}
   126  
   127  //
   128  // Coroutine management
   129  //
   130  
   131  // Start starts the thread in a goroutine, giving it the callable c to run.  the
   132  // t.Resume() method needs to be called to provide arguments to the callable.
   133  func (t *Thread) Start(c Callable) {
   134  	t.RequireBytes(2 << 10) // A goroutine starts off with 2k stack
   135  	go func() {
   136  		var (
   137  			args []Value
   138  			err  error
   139  		)
   140  		// If there was a panic due to an exceeded quota, we need to end the
   141  		// thread and propagate that panic to the calling thread
   142  		defer func() {
   143  			r := recover()
   144  			if r != nil {
   145  				switch r.(type) {
   146  				case ContextTerminationError:
   147  				case threadClose:
   148  					// This means we want to close the coroutine, so no panic!
   149  					r = nil
   150  				default:
   151  					panic(r)
   152  				}
   153  			}
   154  			t.end(args, err, r)
   155  		}()
   156  		args, err = t.getResumeValues()
   157  		if err == nil {
   158  			next := NewTerminationWith(t.CurrentCont(), 0, true)
   159  			err = t.call(c, args, next)
   160  			args = next.Etc()
   161  		}
   162  	}()
   163  }
   164  
   165  // Status returns the status of a thread (suspended, running or dead).
   166  func (t *Thread) Status() ThreadStatus {
   167  	return t.status
   168  }
   169  
   170  // Resume execution of a suspended thread.  Its status switches to
   171  // running while its caller's status switches to suspended.
   172  func (t *Thread) Resume(caller *Thread, args []Value) ([]Value, error) {
   173  	t.mux.Lock()
   174  	if t.status != ThreadSuspended {
   175  		t.mux.Unlock()
   176  		switch t.status {
   177  		case ThreadDead:
   178  			return nil, errors.New("cannot resume dead thread")
   179  		default:
   180  			return nil, errors.New("cannot resume running thread")
   181  		}
   182  	}
   183  	caller.mux.Lock()
   184  	if caller.status != ThreadOK {
   185  		panic("Caller of thread to resume is not running")
   186  	}
   187  	t.caller = caller
   188  	t.status = ThreadOK
   189  	t.mux.Unlock()
   190  	caller.mux.Unlock()
   191  	t.sendResumeValues(args, nil, nil)
   192  	return caller.getResumeValues()
   193  }
   194  
   195  // Close a suspended thread.  If successful, its status switches to dead.  The
   196  // boolean returned is true if it was possible to close the thread (i.e. it was
   197  // suspended or already dead).  The error is non-nil if there was an error in
   198  // the cleanup process, or if the thread had already stopped with an error
   199  // previously.
   200  func (t *Thread) Close(caller *Thread) (bool, error) {
   201  	t.mux.Lock()
   202  	if t.status != ThreadSuspended {
   203  		t.mux.Unlock()
   204  		switch t.status {
   205  		case ThreadDead:
   206  			return true, t.closeErr
   207  		default:
   208  			return false, nil
   209  		}
   210  	}
   211  	caller.mux.Lock()
   212  	if caller.status != ThreadOK {
   213  		panic("Caller of thread to close is not running")
   214  	}
   215  	// The thread needs to go back to running to empty its close stack, before
   216  	// becoming dead.
   217  	t.caller = caller
   218  	t.status = ThreadOK
   219  	t.mux.Unlock()
   220  	caller.mux.Unlock()
   221  	t.sendResumeValues(nil, nil, threadClose{})
   222  	_, err := caller.getResumeValues()
   223  	return true, err
   224  }
   225  
   226  // Yield to the caller thread.  The yielding thread's status switches to
   227  // suspended.  The caller's status must be OK.
   228  func (t *Thread) Yield(args []Value) ([]Value, error) {
   229  	t.mux.Lock()
   230  	if t.status != ThreadOK {
   231  		panic("Thread to yield is not running")
   232  	}
   233  	caller := t.caller
   234  	if caller == nil {
   235  		t.mux.Unlock()
   236  		return nil, errors.New("cannot yield from main thread")
   237  	}
   238  	caller.mux.Lock()
   239  	if caller.status != ThreadOK {
   240  		panic("Caller of thread to yield is not OK")
   241  	}
   242  	t.status = ThreadSuspended
   243  	t.caller = nil
   244  	t.mux.Unlock()
   245  	caller.mux.Unlock()
   246  	caller.sendResumeValues(args, nil, nil)
   247  	return t.getResumeValues()
   248  }
   249  
   250  // This turns off the thread, cleaning up its close stack.  The thread must be
   251  // running.
   252  func (t *Thread) end(args []Value, err error, exception interface{}) {
   253  	caller := t.caller
   254  	t.mux.Lock()
   255  	caller.mux.Lock()
   256  	defer t.mux.Unlock()
   257  	defer caller.mux.Unlock()
   258  	switch {
   259  	case t.status != ThreadOK:
   260  		panic("Called Thread.end on a non-running thread")
   261  	case caller.status != ThreadOK:
   262  		panic("Caller thread of ending thread is not OK")
   263  	}
   264  	close(t.resumeCh)
   265  	t.status = ThreadDead
   266  	t.caller = nil
   267  	err = t.cleanupCloseStack(nil, 0, err) // TODO: not nil
   268  	t.closeErr = err
   269  	caller.sendResumeValues(args, err, exception)
   270  	t.ReleaseBytes(2 << 10) // The goroutine will terminate after this
   271  }
   272  
   273  func (t *Thread) call(c Callable, args []Value, next Cont) error {
   274  	cont := c.Continuation(t, next)
   275  	t.Push(cont, args...)
   276  	return t.RunContinuation(cont)
   277  }
   278  
   279  func (t *Thread) getResumeValues() ([]Value, error) {
   280  	res := <-t.resumeCh
   281  	if res.exception != nil {
   282  		panic(res.exception)
   283  	}
   284  	return res.args, res.err
   285  }
   286  
   287  func (t *Thread) sendResumeValues(args []Value, err error, exception interface{}) {
   288  	t.resumeCh <- valuesError{args: args, err: err, exception: exception}
   289  }
   290  
   291  //
   292  // Calling
   293  //
   294  
   295  // CallContext pushes a new runtime context on the thread's runtime and attempts
   296  // to run f() in the thread.  If the context runs out of resources while f() is
   297  // running, all operations should abort and the CallContext should return
   298  // immediately and not finalizing pending to-be-closed values.
   299  //
   300  // Otherwise (even if f() returns an error), pending to-be-closed values should
   301  // be finalized.
   302  //
   303  // See quotas.md for details about this API.
   304  func (t *Thread) CallContext(def RuntimeContextDef, f func() error) (ctx RuntimeContext, err error) {
   305  	t.PushContext(def)
   306  	c, h := t.CurrentCont(), t.closeStack.size()
   307  	defer func() {
   308  		ctx = t.PopContext()
   309  		if r := recover(); r != nil {
   310  			t.closeStack.truncate(h) // No resources to run that, so just discard it.
   311  			termErr, ok := r.(ContextTerminationError)
   312  			if !ok {
   313  				panic(r)
   314  			}
   315  			err = termErr
   316  		}
   317  	}()
   318  	err = t.cleanupCloseStack(c, h, f())
   319  	if t.GCPolicy() == IsolateGCPolicy {
   320  		t.runFinalizers(t.weakRefPool.ExtractAllMarkedFinalize())
   321  	}
   322  	if err != nil {
   323  		t.setStatus(StatusError)
   324  	}
   325  	return
   326  }
   327  
   328  //
   329  // close stack operations
   330  //
   331  
   332  type closeStack struct {
   333  	stack []Value
   334  }
   335  
   336  func (s closeStack) size() int {
   337  	return len(s.stack)
   338  }
   339  
   340  func (s *closeStack) push(v Value) {
   341  	s.stack = append(s.stack, v)
   342  }
   343  
   344  func (s *closeStack) pop() (Value, bool) {
   345  	sz := len(s.stack)
   346  	if sz == 0 {
   347  		return NilValue, false
   348  	}
   349  	sz--
   350  	v := s.stack[sz]
   351  	s.stack = s.stack[:sz]
   352  	return v, true
   353  }
   354  
   355  func (s *closeStack) truncate(h int) {
   356  	sz := len(s.stack)
   357  	if sz > h {
   358  		s.stack = s.stack[:h]
   359  	}
   360  }
   361  
   362  // Truncate the close stack to size h, calling the __close metamethods in the
   363  // context of the given continuation c and feeding them with the given error.
   364  func (t *Thread) cleanupCloseStack(c Cont, h int, err error) error {
   365  	closeStack := &t.closeStack
   366  	for closeStack.size() > h {
   367  		v, _ := closeStack.pop()
   368  		if Truth(v) {
   369  			closeErr, ok := Metacall(t, v, "__close", []Value{v, ErrorValue(err)}, NewTerminationWith(c, 0, false))
   370  			if !ok {
   371  				return errors.New("to be closed value missing a __close metamethod")
   372  			}
   373  			if closeErr != nil {
   374  				err = closeErr
   375  			}
   376  		}
   377  	}
   378  	return err
   379  }
   380  
   381  //
   382  // messageHandlerCont is a continuation that handles an error message (i.e.
   383  // turns it to handled).
   384  //
   385  type messageHandlerCont struct {
   386  	c    Cont
   387  	err  Value
   388  	done bool
   389  }
   390  
   391  func newMessageHandlerCont(c Cont) *messageHandlerCont {
   392  	return &messageHandlerCont{c: c}
   393  }
   394  
   395  var _ Cont = (*messageHandlerCont)(nil)
   396  
   397  func (c *messageHandlerCont) DebugInfo() *DebugInfo {
   398  	return c.c.DebugInfo()
   399  }
   400  
   401  func (c *messageHandlerCont) Next() Cont {
   402  	return c.c.Next()
   403  }
   404  
   405  func (c *messageHandlerCont) Parent() Cont {
   406  	return c.Next()
   407  }
   408  
   409  func (c *messageHandlerCont) Push(r *Runtime, v Value) {
   410  	if !c.done {
   411  		c.done = true
   412  		c.err = v
   413  	}
   414  }
   415  
   416  func (c *messageHandlerCont) PushEtc(r *Runtime, etc []Value) {
   417  	if c.done || len(etc) == 0 {
   418  		return
   419  	}
   420  	c.Push(r, etc[0])
   421  }
   422  
   423  func (c *messageHandlerCont) RunInThread(t *Thread) (Cont, error) {
   424  	return nil, newHandledError(c.err)
   425  }