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

     1  package runtimelib
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/arnodel/golua/lib/packagelib"
     9  	rt "github.com/arnodel/golua/runtime"
    10  )
    11  
    12  var LibLoader = packagelib.Loader{
    13  	Load: load,
    14  	Name: "runtime",
    15  }
    16  
    17  func load(r *rt.Runtime) (rt.Value, func()) {
    18  	if !rt.QuotasAvailable {
    19  		return rt.NilValue, nil
    20  	}
    21  	pkg := rt.NewTable()
    22  
    23  	rt.SolemnlyDeclareCompliance(
    24  		rt.ComplyCpuSafe|rt.ComplyMemSafe|rt.ComplyTimeSafe|rt.ComplyIoSafe,
    25  
    26  		r.SetEnvGoFunc(pkg, "callcontext", callcontext, 2, true),
    27  		r.SetEnvGoFunc(pkg, "context", context, 0, false),
    28  		r.SetEnvGoFunc(pkg, "killcontext", killnow, 1, false),
    29  		r.SetEnvGoFunc(pkg, "stopcontext", stopnow, 1, false),
    30  		r.SetEnvGoFunc(pkg, "contextdue", due, 1, false),
    31  	)
    32  
    33  	createContextMetatable(r)
    34  
    35  	return rt.TableValue(pkg), nil
    36  }
    37  
    38  func context(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
    39  	ctx := newContextValue(t.Runtime, t.RuntimeContext())
    40  	return c.PushingNext1(t.Runtime, ctx), nil
    41  }
    42  
    43  func callcontext(t *rt.Thread, c *rt.GoCont) (next rt.Cont, retErr error) {
    44  	quotas, err := c.TableArg(0)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	var (
    49  		flagsV      = quotas.Get(rt.StringValue("flags"))
    50  		limitsV     = quotas.Get(rt.StringValue("kill"))
    51  		softLimitsV = quotas.Get(rt.StringValue("stop"))
    52  		hardLimits  rt.RuntimeResources
    53  		softLimits  rt.RuntimeResources
    54  		f           = c.Arg(1)
    55  		fArgs       = c.Etc()
    56  		flags       rt.ComplianceFlags
    57  	)
    58  	if !limitsV.IsNil() {
    59  		var err error
    60  		hardLimits, err = getResources(t, limitsV)
    61  		if err != nil {
    62  			return nil, err
    63  		}
    64  	}
    65  	if !softLimitsV.IsNil() {
    66  		var err error
    67  		softLimits, err = getResources(t, softLimitsV)
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  	}
    72  	if !flagsV.IsNil() {
    73  		flagsStr, ok := flagsV.TryString()
    74  		if !ok {
    75  			return nil, errors.New("flags must be a string")
    76  		}
    77  		for _, name := range strings.Fields(flagsStr) {
    78  			flags, ok = flags.AddFlagWithName(name)
    79  			if !ok {
    80  				return nil, fmt.Errorf("unknown flag: %q", name)
    81  			}
    82  		}
    83  	}
    84  
    85  	next = c.Next()
    86  	res := rt.NewTerminationWith(c, 0, true)
    87  
    88  	ctx, err := t.CallContext(rt.RuntimeContextDef{
    89  		HardLimits:    hardLimits,
    90  		SoftLimits:    softLimits,
    91  		RequiredFlags: flags,
    92  	}, func() error {
    93  		return rt.Call(t, f, fArgs, res)
    94  	})
    95  	t.Push1(next, newContextValue(t.Runtime, ctx))
    96  	switch ctx.Status() {
    97  	case rt.StatusDone:
    98  		t.Push(next, res.Etc()...)
    99  	case rt.StatusError:
   100  		t.Push1(next, rt.ErrorValue(err))
   101  	}
   102  	return next, nil
   103  }
   104  
   105  func getResources(t *rt.Thread, resources rt.Value) (res rt.RuntimeResources, err error) {
   106  	res.Cpu, err = getResVal(t, resources, cpuString)
   107  	if err != nil {
   108  		return
   109  	}
   110  	res.Memory, err = getResVal(t, resources, memoryString)
   111  	if err != nil {
   112  		return
   113  	}
   114  	res.Millis, err = getTimeVal(t, resources)
   115  	if err != nil {
   116  		return
   117  	}
   118  	return
   119  }
   120  
   121  func getResVal(t *rt.Thread, resources rt.Value, key rt.Value) (uint64, error) {
   122  	val, err := rt.Index(t, resources, key)
   123  	if err != nil {
   124  		return 0, err
   125  	}
   126  	return validateResVal(key, val)
   127  }
   128  
   129  func validateResVal(key rt.Value, val rt.Value) (uint64, error) {
   130  	if val.IsNil() {
   131  		return 0, nil
   132  	}
   133  	n, ok := rt.ToIntNoString(val)
   134  	if !ok {
   135  		name, _ := key.ToString()
   136  		return 0, fmt.Errorf("%s must be an integer", name)
   137  	}
   138  	if n <= 0 {
   139  		name, _ := key.ToString()
   140  		return 0, fmt.Errorf("%s must be a positive integer", name)
   141  	}
   142  	return uint64(n), nil
   143  }
   144  
   145  func getTimeVal(t *rt.Thread, resources rt.Value) (uint64, error) {
   146  	val, err := rt.Index(t, resources, secondsString)
   147  	if err != nil {
   148  		return 0, err
   149  	}
   150  	if !val.IsNil() {
   151  		return validateTimeVal(val, 1000, secondsName)
   152  	}
   153  	val, err = rt.Index(t, resources, millisString)
   154  	if err != nil {
   155  		return 0, err
   156  	}
   157  	return validateTimeVal(val, 1, millisName)
   158  }
   159  
   160  func validateTimeVal(val rt.Value, factor float64, name string) (uint64, error) {
   161  	if val.IsNil() {
   162  		return 0, nil
   163  	}
   164  	s, ok := rt.ToFloat(val)
   165  	if !ok {
   166  		return 0, fmt.Errorf("%s must be a numeric value", name)
   167  	}
   168  	if s <= 0 {
   169  		return 0, fmt.Errorf("%s must be positive", name)
   170  	}
   171  	return uint64(s * factor), nil
   172  }
   173  
   174  const (
   175  	secondsName = "seconds"
   176  	millisName  = "millis"
   177  	cpuName     = "cpu"
   178  	memoryName  = "memory"
   179  )
   180  
   181  var (
   182  	secondsString = rt.StringValue(secondsName)
   183  	millisString  = rt.StringValue(millisName)
   184  	cpuString     = rt.StringValue(cpuName)
   185  	memoryString  = rt.StringValue(memoryName)
   186  )