mirror of
https://github.com/RoganDawes/P4wnP1_aloa.git
synced 2025-03-29 19:11:45 +01:00
Introduced context cancellation + propagation to HIDScript controller;Global management for background jobs for HIDScript;Updated ToDo; Updated hid tests
This commit is contained in:
parent
26f436a607
commit
5a7a0b352d
7
ToDo.txt
7
ToDo.txt
@ -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
|
||||
|
212
hid/AsyncOtto.go
212
hid/AsyncOtto.go
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
155
testhid.go
155
testhid.go
@ -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)}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user