Introduced context cancellation + propagation to HIDScript controller;Global management for background jobs for HIDScript;Updated ToDo; Updated hid tests

This commit is contained in:
mame82 2018-06-29 12:29:19 +00:00
parent 26f436a607
commit 5a7a0b352d
9 changed files with 321 additions and 251 deletions

View File

@ -16,6 +16,11 @@ HID
- add additional keyboard layouts (not for first release)
- DONE: fix absolute mouse movement
- DONE: avoid memory leak on reinit of HIDController
- DONE: implement job management
- DONE: implement context for cancellation and cancellation propagation
- blocking JavaScript to go callbacks (waitLED, delay, waitLEDRepeated) have to be job-context aware to allow
interruption (the Interrupt channel of Otto only triggers after a blocking callback is evaluated, thus this callbacks have
to be interrupted themselves)
WIFI
- implement connection to OPEN-AUTH network as STA
@ -23,4 +28,4 @@ WIFI
TO FIX:
- debug out of HIDScript puts way to much CPU load on journaling daemon (floods logs)
- allow gRPC server to abort Running HID scripts on context.DONE

View File

@ -5,8 +5,57 @@ import (
"errors"
"fmt"
"sync"
"log"
"context"
"encoding/json"
)
var vmNum = 0
var jobNum = 0
type AsyncOttoJob struct {
ctx context.Context
Cancel context.CancelFunc
executingVM *AsyncOttoVM
Id int
Source interface{}
finishedNotify chan struct{}
isFinished bool //finishedNotify could only be used to signal finish (blocking wait for chanel close), not for non blocking polling, thus we add a state var
ResultErr error
ResultValue otto.Value
}
func (job *AsyncOttoJob) Result() interface{} {
goRes,_ := job.ResultValue.Export() //error is always nil (otto doc)
return goRes
}
func (job *AsyncOttoJob) SetFinished() {
job.executingVM = nil //avoid accessing the vm from this job again
job.isFinished = true
close(job.finishedNotify)
}
func (job *AsyncOttoJob) WaitFinished() {
<-job.finishedNotify
}
func (job *AsyncOttoJob) WaitResult() (otto.Value, error) {
job.WaitFinished()
return job.ResultValue, job.ResultErr
}
func (job *AsyncOttoJob) ResultJsonString() (string, error) {
goRes := job.Result() //error is always nil (otto doc)
json, err := json.Marshal(goRes)
if err != nil {return "",err}
return string(json),nil
}
type AsyncOttoVM struct {
vm *otto.Otto
ResultErr *error
@ -14,16 +63,20 @@ type AsyncOttoVM struct {
Finished chan bool
isWorking bool
sync.Mutex
Id int
}
func NewAsyncOttoVM(vm *otto.Otto) *AsyncOttoVM {
vm.Interrupt = make(chan func(), 1) //set Interrupt channel
return &AsyncOttoVM{
isWorking: false,
ResultValue: otto.Value{},
Finished: make(chan bool,1), //buffer of 1 as we don't want to block on setting the finished signal
vm: vm,
res := &AsyncOttoVM{
isWorking: false,
ResultValue: otto.Value{},
Finished: make(chan bool,1), //buffer of 1 as we don't want to block on setting the finished signal
vm: vm,
Id: vmNum,
}
vmNum++
return res
}
func NewAsyncOttoVMClone(vm *otto.Otto) *AsyncOttoVM {
@ -35,111 +88,94 @@ func (avm AsyncOttoVM) IsWorking() bool {
res := avm.isWorking
avm.Unlock()
return res
}
func (avm *AsyncOttoVM) Run(src interface{}) (val otto.Value, res error) {
res = avm.RunAsync(src)
if res != nil { return }
return avm.WaitResult()
}
func (avm *AsyncOttoVM) RunAsync(src interface{}) (error) {
func (avm *AsyncOttoVM) SetWorking(working bool) {
avm.Lock()
avm.isWorking = true
// ToDo: This has to replaced by real job control, to preserve results in case waitResult() is called late (results have to be stored per job, not per VM)
for len(avm.Finished) > 0 {
<-avm.Finished
} // We consume old finish events (there was no call to waitResult() up to that point)
avm.ResultErr = nil
avm.ResultValue = otto.Value{}
avm.isWorking = working
avm.Unlock()
fmt.Printf("VM %d set to isWorking: %v\n", avm.Id, working)
return
}
func (avm *AsyncOttoVM) RunAsync(ctx context.Context, src interface{}) (job *AsyncOttoJob, err error) {
if avm.IsWorking() {
return job, errors.New(fmt.Sprintf("VM %d couldn't start new job, because it is still running one"))
}
avm.SetWorking(true)
//create job
jobNum++ //jobs start from 1 not 0
ctx,cancel := context.WithCancel(ctx)
job = &AsyncOttoJob{
Id: jobNum,
ctx: ctx,
Cancel: cancel,
executingVM: avm,
Source: src,
finishedNotify: make(chan struct{}),
}
fmt.Printf("RunAsync called for VM %d\n", avm.Id)
go func(avm *AsyncOttoVM) {
select {
case <-job.ctx.Done():
if job.executingVM != nil {
job.executingVM.vm.Interrupt <- func() {
log.Printf("VM %d EXECUTED INTERRUPT FUNCTION\n", avm.Id)
panic(halt)
}
}
}
}(avm)
defer func() {
go func(avm *AsyncOttoVM) {
defer func() { //runs after avm.vm.Run() returns (because script finished a was interrupted)
defer avm.SetWorking(false)
if caught := recover(); caught != nil {
fmt.Printf("VM %d CAUGHT INTERRUPT, ENDING JOB %d\n", avm.Id, job.Id)
if caught == halt {
nErr := errors.New("VM execution cancelled")
avm.ResultErr = &nErr
job.ResultErr = errors.New(fmt.Sprintf("Execution of job %d on VM %d interrupted\n", job.Id, avm.Id))
avm.Lock()
avm.isWorking = false
avm.Unlock()
//destroy reference to Otto
//avm.vm = nil
//signal finished
avm.Finished <- true
// signal Job finished
job.SetFinished()
return
}
panic(caught) //re-raise panic, as it isn't `halt`
}
avm.Lock()
avm.isWorking = false
avm.Unlock()
return
}()
// fmt.Println("Running vm")
tmpValue, tmpErr := avm.vm.Run(src)
avm.ResultValue = tmpValue //store value first, to have it accessible when error is retrieved from channel
avm.ResultErr = &tmpErr
avm.Finished <- true
// fmt.Println("Stored vm result")
fmt.Printf("START JOB %d SCRIPT ON VM %d\n", job.Id, avm.Id) //DEBUG
//short pre-run to set JobID and VMID (ignore errors)
avm.vm.Run(fmt.Sprintf("JID=%d;VMID=%d;", job.Id, avm.Id))
job.ResultValue, job.ResultErr = avm.vm.Run(job.Source) //store result
job.SetFinished() // signal job finished
//avm.setIsWorking(false)
}(avm)
return nil
}
func (avm *AsyncOttoVM) WaitResult() (val otto.Value, err error) {
//Always run async
/*
if !avm.runningAsync {
return otto.Value{},errors.New("AsyncVM isn't running an async script from which a result could be received")
}
*/
if avm.vm == nil {
return val,errors.New("Async vm isn't valid anymore")
}
//wait for finished signal
<- avm.Finished
//destroy reference to vm
//avm.vm = nil
return avm.ResultValue, *avm.ResultErr
}
func (avm *AsyncOttoVM) Cancel() error {
if avm.vm == nil {
return errors.New("Async vm isn't valid anymore")
}
// Note: in between here, there's a small race condition, if avm.vm is set to nil
// after the check above (f.e. the vm returns an result and thus the pointer is set to nil,
// right after the check)
//interrupt vm
if avm.IsWorking() {
if len(avm.vm.Interrupt) == 0 {
fmt.Println("SENDING IRQ TO VM")
avm.vm.Interrupt <- func() {
panic(halt)
//DEBUG
if job.ResultErr == nil {
jRes,jErr := job.ResultJsonString()
if jErr == nil {
fmt.Printf("JOB %d on VM %d SUCCEEDED WITH RESULT: %s\n", job.Id, avm.Id, jRes)
} else {
fmt.Printf("JOB %d on VM %d SUCCEEDED BUT RESULT COULDN'T BE MARSHALED TO JSON: %v\n", job.Id, avm.Id, jErr)
}
} else {
fmt.Printf("JOB %d on VM %d FAILED: %v\n", job.Id, avm.Id, job.ResultErr)
}
//consume result
avm.WaitResult()
}
}(avm)
return nil
return job,nil
}
func (avm *AsyncOttoVM) Run(ctx context.Context,src interface{}) (val otto.Value, res error) {
job,err := avm.RunAsync(ctx, src)
if err != nil { return val,err }
return job.WaitResult()
}

View File

@ -4,9 +4,11 @@ package hid
import (
"errors"
"github.com/robertkrimen/otto"
"fmt"
"context"
"sync"
"log"
"time"
"fmt"
)
@ -16,6 +18,9 @@ const (
)
var (
globalJobList = make(map[*AsyncOttoJob]bool)
globalJobListMutex = sync.Mutex{}
hidControllerReuse *HIDController
halt = errors.New("Stahp")
@ -49,9 +54,12 @@ type HIDController struct {
Mouse *Mouse
vmPool [MAX_VM]*AsyncOttoVM //ToDo: check if this could be changed to sync.Pool
vmMaster *otto.Otto
ctx context.Context
cancel context.CancelFunc
}
func NewHIDController(keyboardDevicePath string, keyboardMapPath string, mouseDevicePath string) (ctl *HIDController, err error) {
func NewHIDController(ctx context.Context, keyboardDevicePath string, keyboardMapPath string, mouseDevicePath string) (ctl *HIDController, err error) {
//ToDo: check if hidcontroller could work with a context whith cancellation, which then could be cancelled on reuse of the object
if hidControllerReuse == nil {
hidControllerReuse = &HIDController{}
@ -69,12 +77,15 @@ func NewHIDController(keyboardDevicePath string, keyboardMapPath string, mouseDe
}
ctx,cancel := context.WithCancel(ctx)
hidControllerReuse.ctx = ctx
hidControllerReuse.cancel = cancel
//Note: to disable mouse/keyboard support, the respective device path has to have zero length
//init keyboard
if len(keyboardDevicePath) > 0 {
hidControllerReuse.Keyboard, err = NewKeyboard(keyboardDevicePath, keyboardMapPath)
hidControllerReuse.Keyboard, err = NewKeyboard(ctx, keyboardDevicePath, keyboardMapPath)
if err != nil { return nil, err }
}
@ -95,91 +106,97 @@ func (ctl *HIDController) Abort() {
hidControllerReuse.Keyboard.Close() //interrupts go routines reading from device file and lets LEDStateListeners die
}
// Interrupt all VMs already running
//hidControllerReuse.CancelAllBackgroundJobs()
hidControllerReuse.CancelAllBackgroundJobs()
}
func (ctl *HIDController) NextUnusedVM() (idx int, vm *AsyncOttoVM, err error) {
func (ctl *HIDController) NextUnusedVM() (vm *AsyncOttoVM, err error) {
//iterate over pool
for idx,avm := range ctl.vmPool {
for _,avm := range ctl.vmPool {
if !avm.IsWorking() {
//return first non-working vm
//set job ID as JID
avm.vm.Set("JID", idx)
return idx, avm, nil //free to be used
return avm, nil //free to be used
}
}
return 0, nil, errors.New("No free JavaScript VM available in pool")
return nil, errors.New("No free JavaScript VM available in pool")
}
func (ctl *HIDController) RunScript(script string) (val otto.Value, err error) {
func (ctl *HIDController) RunScript(ctx context.Context, script string) (val otto.Value, err error) {
//fetch next free vm from pool
_,avm,err := ctl.NextUnusedVM()
avm,err := ctl.NextUnusedVM()
if err != nil { return otto.Value{}, err }
val, err = avm.Run(script)
val, err = avm.Run(ctx, script)
return
}
func (ctl *HIDController) StartScriptAsBackgroundJob(script string) (avmId int, avm *AsyncOttoVM, err error) {
func (ctl *HIDController) GetBackgroundJobByID(id int) (job *AsyncOttoJob, err error) {
globalJobListMutex.Lock()
for j,_ := range globalJobList {
if j.Id == id {
job = j
break
}
}
globalJobListMutex.Unlock()
if job == nil {
return nil, errors.New(fmt.Sprintf("Job with ID %d not found in list of background jobs\n", id))
} else {
return job,nil
}
}
func (ctl *HIDController) StartScriptAsBackgroundJob(ctx context.Context,script string) (job *AsyncOttoJob, err error) {
//fetch next free vm from pool
avmId,avm,err = ctl.NextUnusedVM()
if err != nil { return 0, nil, err }
avm,err := ctl.NextUnusedVM()
if err != nil { return nil, err }
//try to run script async
err = avm.RunAsync(script)
if err != nil { return 0, nil, err }
job,err = avm.RunAsync(ctx,script)
if err != nil { return nil, err }
//add job to global list
globalJobListMutex.Lock()
globalJobList[job] = true
globalJobListMutex.Unlock()
return
}
func (ctl *HIDController) CancelBackgroundJob(jobId int) (err error) {
if jobId < 0 || jobId >= MAX_VM {
return errors.New("Invalid Id for AsyncOttoVM")
func (ctl *HIDController) WaitBackgroundJobResult(job *AsyncOttoJob) (val otto.Value, err error) {
globalJobListMutex.Lock()
if !globalJobList[job] {
err = errors.New(fmt.Sprintf("Tried to retrieve results of job with id %d failed, because it is not in the list of background jobs", job.Id))
}
return ctl.vmPool[jobId].Cancel()
}
globalJobListMutex.Unlock()
if err != nil {return}
func (ctl *HIDController) WaitBackgroundJobResult(avmId int) (otto.Value, error) {
if avmId < 0 || avmId >= MAX_VM {
return otto.Value{}, errors.New("Invalid Id for AsyncOttoVM")
}
return ctl.vmPool[avmId].WaitResult()
}
val,err = job.WaitResult() //Blocking result wait
//remove job from global list after result has been retrieved
globalJobListMutex.Lock()
delete(globalJobList,job)
globalJobListMutex.Unlock()
func (ctl *HIDController) GetRunningBackgroundJobs() (res []int) {
res = make([]int,0)
for i := 0; i< MAX_VM; i++ {
if ctl.vmPool[i].IsWorking() {
res = append(res, i)
}
}
return
}
func (ctl *HIDController) currentlyWorkingVMs() (res []*AsyncOttoVM) {
res = make([]*AsyncOttoVM,0)
for i := 0; i< MAX_VM; i++ {
if ctl.vmPool[i].IsWorking() {
res = append(res, ctl.vmPool[i])
}
func (ctl *HIDController) CancelAllBackgroundJobs() {
globalJobListMutex.Lock()
oldList := globalJobList
// cancel known jobs
for job,_ := range oldList {
fmt.Printf("Cancelling Job %d\n", job.Id)
job.Cancel()
}
return
}
globalJobList = make(map[*AsyncOttoJob]bool) //Create new empty list
globalJobListMutex.Unlock()
func (ctl *HIDController) CancelAllBackgroundJobs() error {
for i := 0; i< MAX_VM; i++ {
fmt.Printf("CANCELLING VM %d: %+v\n",i, ctl.vmPool[i])
if ctl.vmPool[i].IsWorking() {
ctl.vmPool[i].Cancel()
}
}
return nil
}
//Function declarations for master VM

View File

@ -13,6 +13,7 @@ import (
"regexp"
"path/filepath"
"sync"
"context"
)
var (
@ -38,19 +39,25 @@ type HIDKeyboard struct {
LEDWatcher *KeyboardLEDStateWatcher
KeyDelay int
KeyDelayJitter int
ctx context.Context
cancel context.CancelFunc
}
func NewKeyboard(devicePath string, resourcePath string) (keyboard *HIDKeyboard, err error) {
func NewKeyboard(ctx context.Context, devicePath string, resourcePath string) (keyboard *HIDKeyboard, err error) {
//ToDo: check existence of deviceFile (+ is writable)
ctx,cancel := context.WithCancel(ctx)
keyboard = &HIDKeyboard{
lock: &sync.Mutex{},
DevicePath: devicePath,
KeyDelay: 0,
KeyDelayJitter: 0,
ctx: ctx,
cancel: cancel,
}
//Load available language maps
@ -58,7 +65,7 @@ func NewKeyboard(devicePath string, resourcePath string) (keyboard *HIDKeyboard,
if err != nil {return nil, err}
//Init LED sate
keyboard.LEDWatcher, err = NewLEDStateWatcher(devicePath)
keyboard.LEDWatcher, err = NewLEDStateWatcher(ctx, devicePath)
if err != nil {return nil, err}
return

View File

@ -6,6 +6,7 @@ import (
"time"
"log"
"fmt"
"context"
)
var (
@ -68,21 +69,22 @@ type lockableListenerMap struct {
}
type KeyboardLEDStateWatcher struct {
ledState *HIDLEDState //latest global LED state
listeners *lockableListenerMap //map of registered listeners
ledStateFile *os.File
hasInitialSate bool //marks if the initial state is define (gets true after first LED state ha been read)
ledState *HIDLEDState //latest global LED state
listeners *lockableListenerMap //map of registered listeners
ledStateFile *os.File
hasInitialState bool //marks if the initial state is define (gets true after first LED state ha been read)
//listener which should be registered are put into a zero length channel, to allow blocking till a listener is added
//in case there isn't already a listener (avoid reading LED states, without having a single listener consuming them)
listenersToAdd chan *KeyboardLEDStateListener
listenersToAdd chan *KeyboardLEDStateListener
readerToDispatcher chan HIDLEDState
interrupt chan struct{}
isUsable bool
ctx context.Context
cancelFunc context.CancelFunc
isUsable bool
}
func NewLEDStateWatcher(devFilePath string) (res *KeyboardLEDStateWatcher,err error) {
func NewLEDStateWatcher(ctx context.Context, devFilePath string) (res *KeyboardLEDStateWatcher,err error) {
//try to open the devFile
devFile, err := os.Open(devFilePath)
if err != nil { return }
@ -91,20 +93,21 @@ func NewLEDStateWatcher(devFilePath string) (res *KeyboardLEDStateWatcher,err er
privateCurrentKeyboardLEDWatcher.Stop()
}
ctx,cancel := context.WithCancel(ctx)
res = &KeyboardLEDStateWatcher{
ledState: &HIDLEDState{},
hasInitialSate: false,
ledStateFile: devFile,
ledState: &HIDLEDState{},
hasInitialState: false,
ledStateFile: devFile,
listeners: &lockableListenerMap{
m: make(map[*KeyboardLEDStateListener]bool),
},
// Buffer at least one listener, to avoid blocking when one is added, the channel is only used to block the
// dispatchLoop, in case there's no registered listener (by reading from the listenerToAdd chanel
listenersToAdd: make(chan *KeyboardLEDStateListener,1),
listenersToAdd: make(chan *KeyboardLEDStateListener,1),
readerToDispatcher: make(chan HIDLEDState), //communicates new LED states to from file reader loop to dispatcher loop, blocks till consumed
interrupt: make(chan struct{},1), // used to interrupt reader and dispatcher loop
ctx: ctx,
cancelFunc: cancel,
@ -130,7 +133,7 @@ func (w *KeyboardLEDStateWatcher) RetrieveNewListener() (l *KeyboardLEDStateList
l = &KeyboardLEDStateListener{
isMarkedForDeletion: false,
changedLeds: make(chan HIDLEDState),
interrupt: make(chan struct{}),
//interrupt: make(chan struct{}),
ledWatcher: w,
}
@ -168,8 +171,8 @@ func (w *KeyboardLEDStateWatcher) readLoop() {
//mark watcher as unusable
w.isUsable = false
//interrupt the dispatcher loop, by closing the interrupt channel
close(w.interrupt)
//interrupt the dispatcher loop, by cancelling the context
w.cancelFunc()
break
}
@ -196,9 +199,9 @@ func (w *KeyboardLEDStateWatcher) dispatchLoop() {
//Translate received LED state to state change (if first received state, everything is considered as change
ledStateChange := w.ledState.Changes(newState)
if w.hasInitialSate == false {
if w.hasInitialState == false {
ledStateChange.fillState(MaskAny)
w.hasInitialSate = true
w.hasInitialState = true
}
//Store new LED state
@ -250,7 +253,7 @@ func (w *KeyboardLEDStateWatcher) dispatchLoop() {
delete(w.listeners.m, l)
}
w.listeners.Unlock()
case <- w.interrupt:
case <- w.ctx.Done():
//inform all listeners about the interrupt
w.listeners.Lock()
@ -276,7 +279,7 @@ func (w *KeyboardLEDStateWatcher) dispatchLoop() {
type KeyboardLEDStateListener struct {
ledWatcher *KeyboardLEDStateWatcher //the parent LEDWatcher, containing global ledState
changedLeds chan HIDLEDState //changedLeds represents the LEDs which change since last report as bitfield (MaskNumLock, MaskCapsLock ...) the actual state has to be fetched from the respective field of the ledWatcher.ledState
interrupt chan struct{}
//interrupt chan struct{}
isMarkedForDeletion bool
}
@ -296,7 +299,7 @@ func (l *KeyboardLEDStateListener) Unregister() {
}
//close channels
close(l.interrupt)
//close(l.interrupt)
close(l.changedLeds)
}
@ -340,7 +343,7 @@ func (kbd *HIDKeyboard) WaitLEDStateChange(intendedChange byte, timeout time.Dur
return &relevantChanges, nil
}
//If here, there was a LED state change, but not one we want to use for triggering (continue outer loop, consuming channel data)
case <-l.interrupt:
case <-l.ledWatcher.ctx.Done():
return nil, ErrAbort
case <- time.After(remaining):
return nil, ErrTimeout
@ -449,7 +452,7 @@ func (kbd *HIDKeyboard) WaitLEDStateChangeRepeated(intendedChange byte, repeatCo
//return &relevantChanges, nil
}
//If here, there was a LED state change, but not one we want to use for triggering (continue outer loop, consuming channel data)
case <-l.interrupt:
case <-l.ledWatcher.ctx.Done():
return nil, ErrAbort
case <- time.After(remaining):
return nil, ErrTimeout

View File

@ -6,7 +6,6 @@ import (
"os"
"math"
"time"
"fmt"
)
const (
@ -37,7 +36,7 @@ func NewMouse(devicePath string) (mouse *Mouse, err error) {
func (m *Mouse) writeReportToFile(file string) error {
report, err := generateMouseReport(m.lastChangeWasAbsolute, m.buttons, m.axis)
if err != nil { return err }
fmt.Printf("Writing %+v to %s\n", report, file)
//fmt.Printf("Writing %+v to %s\n", report, file)
return ioutil.WriteFile(file, report, os.ModePerm) //Serialize Report and write to specified file
}

View File

@ -81,7 +81,8 @@ func (s *server) HIDRunScript(ctx context.Context, scriptReq *pb.HIDScriptReques
if scriptFile, err := ioutil.ReadFile(scriptReq.ScriptPath); err != nil {
return nil, errors.New(fmt.Sprintf("Couldn't load HIDScript '%s': %v\n", scriptReq.ScriptPath, err))
} else {
scriptVal,err := HidCtl.RunScript(string(scriptFile))
//ToDo: check influence of request context
scriptVal,err := HidCtl.RunScript(ctx, string(scriptFile))
if err != nil { return nil,err }
val,_ := scriptVal.Export() //Convert to Go representation, error is always nil
jsonVal,err := json.Marshal(val)

View File

@ -14,6 +14,7 @@ import (
"net"
"regexp"
"../hid"
"context"
)
const (
@ -603,7 +604,7 @@ func DeployGadgetSettings(settings pb.GadgetSettings) error {
devPathMouse := HidDevPath[USB_FUNCTION_HID_MOUSE_name]
var errH error
HidCtl, errH = hid.NewHIDController(devPathKeyboard, USB_KEYBOARD_LANGUAGE_MAP_PATH, devPathMouse)
HidCtl, errH = hid.NewHIDController(context.Background(), devPathKeyboard, USB_KEYBOARD_LANGUAGE_MAP_PATH, devPathMouse)
if errH != nil {
log.Printf("ERROR: Couldn't bring up an instance of HIDController for keyboard: '%s', mouse: '%s' and mapping path '%s'\nReason: %v\n", devPathKeyboard, devPathMouse, USB_KEYBOARD_LANGUAGE_MAP_PATH, errH)
} else {

View File

@ -14,6 +14,7 @@ import(
_ "net/http/pprof"
"net/http"
"runtime"
"context"
"io/ioutil"
)
@ -205,7 +206,7 @@ func TestCombinedScript(hidCtl *hid.HIDController) (err error) {
}
`
_,err = hidCtl.RunScript(testcript)
_,err = hidCtl.RunScript(context.Background(),testcript)
if err != nil {panic(err)}
return
@ -245,10 +246,46 @@ func TestMouseCircle(hidCtl *hid.HIDController) {
}
`
_,err := hidCtl.RunScript(scriptMouse)
_,err := hidCtl.RunScript(context.Background(),scriptMouse)
if err != nil { panic(err)}
}
// To profile for memory leaks and test clean cancellation of already running scripts on controller re-init
func TestControllerReInit() {
//Test for memory leaks
hidCtlTests := make([]*hid.HIDController,0)
for i:=0; i<10;i++ {
//create new controller
fmt.Printf("****Creating HIDController %d\n", i)
hidCtlTest,_ := hid.NewHIDController(context.Background(),"/dev/hidg0", "keymaps", "/dev/hidg1")
//run script which utilizes LED read
fmt.Printf("****Starting async LED reading script for HIDController %d\n", i)
//script := "waitLEDRepeat(ANY);"
script := "console.log('...started');delay(3000);console.log('...ended');"
ctx := context.Background()
for i:=0;i<4;i++ {
job,err := hidCtlTest.StartScriptAsBackgroundJob(ctx, script)
if err != nil {
fmt.Printf("Error starting new job: %v\n",err)
} else {
fmt.Printf("New job started: %+v\n",job)
}
}
time.Sleep(time.Second)
//add to slice
hidCtlTests = append(hidCtlTests, hidCtlTest)
}
hidCtlTests = make([]*hid.HIDController,0)
runtime.GC()
}
func main() {
f, err := os.Create("trace.out")
if err != nil {
@ -264,83 +301,11 @@ func main() {
/*
for x:=0; x<50; x++ {
err := mouse.MoveTo(-12,int16(x))
if err != nil { panic(err) }
time.Sleep(100 * time.Millisecond)
}
*/
//Test for memory leaks
hidCtlTests := make([]*hid.HIDController,0)
for i:=0; i<10;i++ {
//create new controller
fmt.Printf("****Creating HIDController %d\n", i)
hidCtlTest,_ := hid.NewHIDController("/dev/hidg0", "keymaps", "/dev/hidg1")
//run script which utilizes LED read
fmt.Printf("****Starting async LED reading script for HIDController %d\n", i)
script := "waitLEDRepeat(ANY);"
hidCtlTest.StartScriptAsBackgroundJob(script)
hidCtlTest.StartScriptAsBackgroundJob(script)
hidCtlTest.StartScriptAsBackgroundJob(script)
hidCtlTest.StartScriptAsBackgroundJob(script)
time.Sleep(time.Second)
//add to slice
hidCtlTests = append(hidCtlTests, hidCtlTest)
//TestControllerReInit()
}
/*
for i,hidCtlTest := range hidCtlTests {
fmt.Printf("****Aborting HIDController %d\n", i)
//hidCtlTest.Stop()
hidCtlTests[i] = nil //destroy ref to allow GC
if i < 5 { time.Sleep(time.Second) }
runtime.GC()
}
*/
hidCtlTests = make([]*hid.HIDController,0)
runtime.GC()
for i:=0; i<10;i++ {
//create new controller
fmt.Printf("****Creating HIDController %d\n", i)
hidCtlTest,_ := hid.NewHIDController("/dev/hidg0", "keymaps", "/dev/hidg1")
//run script which utilizes LED read
fmt.Printf("****Starting async LED reading script for HIDController %d\n", i)
script := "waitLEDRepeat(ANY);"
hidCtlTest.StartScriptAsBackgroundJob(script)
hidCtlTest.StartScriptAsBackgroundJob(script)
hidCtlTest.StartScriptAsBackgroundJob(script)
hidCtlTest.StartScriptAsBackgroundJob(script)
time.Sleep(time.Second)
//add to slice
hidCtlTests = append(hidCtlTests, hidCtlTest)
}
/*
for i,hidCtlTest := range hidCtlTests {
fmt.Printf("****Aborting HIDController %d\n", i)
//hidCtlTest.Stop()
hidCtlTests[i] = nil //destroy ref to allow GC
if i < 5 { time.Sleep(time.Second) }
runtime.GC()
}
*/
hidCtl, err := hid.NewHIDController("/dev/hidg0", "keymaps", "/dev/hidg1")
hidCtl, err := hid.NewHIDController(context.Background(),"/dev/hidg0", "keymaps", "/dev/hidg1")
if err != nil {panic(err)}
@ -363,12 +328,48 @@ func main() {
//TestMouseCircle(hidCtl)
//add bg jobs waiting for LED
jobList := make([]int,0)
fmt.Println("Adding sleeping jobs with 5 seconds timeout context")
ctxT,_ := context.WithTimeout(context.Background(), time.Second * 2)
script := "console.log('START ' + JID + ' on VM ' + VMID);delay(5000);console.log(JID + ' returned from 5s blocking delay');"
startTime := time.Now()
for i:=1; i<4; i++ {
job,err := hidCtl.StartScriptAsBackgroundJob(ctxT,script)
if err != nil {
fmt.Printf("Failed adding background job: %v\n", err)
} else {
// ad job to slice
jobList = append(jobList, job.Id)
}
}
//Wait for all jobs to finish
fmt.Printf("Waiting for Job results for IDs: %+v\n", jobList)
for _,jid := range jobList {
job,err := hidCtl.GetBackgroundJobByID(jid)
if err != nil {
fmt.Printf("Job with ID %d not found, skipping...\n", jid)
continue
} else {
fmt.Printf("Waiting for finish of job with ID %d \n", jid)
jRes,jErr := hidCtl.WaitBackgroundJobResult(job)
fmt.Printf("JID: %d, Result: %+v, Err: %v\n", jid, jRes, jErr)
}
}
fmt.Printf("All results received after %v\n", time.Since(startTime))
//try to load script file
filepath := "./hidtest1.js"
if scriptFile, err := ioutil.ReadFile(filepath); err != nil {
log.Printf("Couldn't load HIDScript testfile: %s\n", filepath)
} else {
_,err = hidCtl.RunScript(string(scriptFile))
_,err = hidCtl.RunScript(context.Background(),string(scriptFile))
if err != nil { panic(err)}
}