HIDScript JS callbacks to go are now reacting to Otto's IRQ channel

This commit is contained in:
mame82 2018-06-29 14:17:41 +00:00
parent 5a7a0b352d
commit 1c7389f77f
5 changed files with 55 additions and 27 deletions

View File

@ -18,9 +18,8 @@ HID
- 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)
- DONE: blocking JavaScript to go callbacks (waitLED, delay, waitLEDRepeated) have to consume the otto.Otto.Interrupt channel
to be able to be aborted when Otto is interrupted
WIFI
- implement connection to OPEN-AUTH network as STA

View File

@ -58,9 +58,6 @@ func (job *AsyncOttoJob) ResultJsonString() (string, error) {
type AsyncOttoVM struct {
vm *otto.Otto
ResultErr *error
ResultValue otto.Value
Finished chan bool
isWorking bool
sync.Mutex
Id int
@ -70,8 +67,6 @@ func NewAsyncOttoVM(vm *otto.Otto) *AsyncOttoVM {
vm.Interrupt = make(chan func(), 1) //set Interrupt channel
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,
}
@ -125,7 +120,7 @@ func (avm *AsyncOttoVM) RunAsync(ctx context.Context, src interface{}) (job *Asy
if job.executingVM != nil {
job.executingVM.vm.Interrupt <- func() {
log.Printf("VM %d EXECUTED INTERRUPT FUNCTION\n", avm.Id)
panic(halt)
panic(haltirq)
}
}
}
@ -136,14 +131,14 @@ func (avm *AsyncOttoVM) RunAsync(ctx context.Context, src interface{}) (job *Asy
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 {
if caught == haltirq {
job.ResultErr = errors.New(fmt.Sprintf("Execution of job %d on VM %d interrupted\n", job.Id, avm.Id))
// signal Job finished
job.SetFinished()
return
}
panic(caught) //re-raise panic, as it isn't `halt`
panic(caught) //re-raise panic, as it isn't `haltirq`
}
return
}()

View File

@ -23,8 +23,9 @@ var (
hidControllerReuse *HIDController
halt = errors.New("Stahp")
ErrAbort = errors.New("Event listening aborted")
haltirq = errors.New("Stahp")
ErrAbort = errors.New("Event listening aborted")
ErrIrq = errors.New("Aborted due to interrupt request")
ErrNotAllowed = errors.New("Calling not allowed (currently disabled)")
)
@ -276,7 +277,6 @@ func (ctl *HIDController) jsTypingSpeed(call otto.FunctionCall) (res otto.Value)
}
func (ctl *HIDController) jsDelay(call otto.FunctionCall) (res otto.Value) {
arg0 := call.Argument(0)
//fmt.Printf("JS delay() called with: `%s` (%s)\n", arg0, arg0)
@ -292,8 +292,14 @@ func (ctl *HIDController) jsDelay(call otto.FunctionCall) (res otto.Value) {
}
delay := int(fDelay)
log.Printf("HIDScript delay: Sleeping `%v` milliseconds\n", delay)
time.Sleep(time.Millisecond * time.Duration(int(delay)))
//time.Sleep(time.Millisecond * time.Duration(int(delay)))
select {
case <- time.After(time.Millisecond * time.Duration(int(delay))):
return
case irq := <-call.Otto.Interrupt:
irq()
}
return
}
@ -357,8 +363,19 @@ func (ctl *HIDController) jsWaitLED(call otto.FunctionCall) (res otto.Value) {
return
}
changed,err := ctl.Keyboard.WaitLEDStateChange(mask, timeout)
//fmt.Printf("Changed %+v\n", changed)
irqCtx, cancel := context.WithCancel(context.Background())
go func(cancel context.CancelFunc, vm *otto.Otto) {
select {
case /*irq :=*/ <- vm.Interrupt:
cancel() //cancel context used by LED state change
//irq() // call irq handler
}
}(cancel,call.Otto)
changed,err := ctl.Keyboard.WaitLEDStateChange(irqCtx, mask, timeout)
if err == ErrIrq {
panic(haltirq)
}
errStr := ""
if err != nil {errStr = fmt.Sprintf("%v",err)}
@ -459,9 +476,21 @@ func (ctl *HIDController) jsWaitLEDRepeat(call otto.FunctionCall) (res otto.Valu
return
}
irqCtx, cancel := context.WithCancel(context.Background())
go func(cancel context.CancelFunc, vm *otto.Otto) {
select {
case /*irq :=*/ <- vm.Interrupt:
cancel() //cancel context used by LED state change
//irq() // call irq handler
}
}(cancel,call.Otto)
log.Printf("HIDScript: Waiting for repeated LED change. Mask for considered LEDs: %v, Minimum repeat count: %v, Maximum repeat delay: %v, Timeout: %v\n", mask, repeatCount, maxInterval, timeout)
changed,err := ctl.Keyboard.WaitLEDStateChangeRepeated(mask, repeatCount, maxInterval, timeout)
//fmt.Printf("Changed %+v\n", changed)
changed,err := ctl.Keyboard.WaitLEDStateChangeRepeated(irqCtx, mask, repeatCount, maxInterval, timeout)
if err == ErrIrq {
panic(haltirq)
}
errStr := ""
if err != nil {errStr = fmt.Sprintf("%v",err)}

View File

@ -308,7 +308,7 @@ Waits for single LED state change
intendedChange: Mask values combined with logical or, to indicate which LEDs are allowed to trigger MaskNu
return value changed: Mask values combined with logical or, indicating which LED actually changed in order to stop waiting
*/
func (kbd *HIDKeyboard) WaitLEDStateChange(intendedChange byte, timeout time.Duration) (changed *HIDLEDState,err error) {
func (kbd *HIDKeyboard) WaitLEDStateChange(ctxIrq context.Context, intendedChange byte, timeout time.Duration) (changed *HIDLEDState,err error) {
//register state change listener
l,err := kbd.LEDWatcher.RetrieveNewListener()
if err!= nil { return nil,err }
@ -345,13 +345,15 @@ func (kbd *HIDKeyboard) WaitLEDStateChange(intendedChange byte, timeout time.Dur
//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.ledWatcher.ctx.Done():
return nil, ErrAbort
case <-ctxIrq.Done():
return nil, ErrIrq
case <- time.After(remaining):
return nil, ErrTimeout
}
}
}
func (kbd *HIDKeyboard) WaitLEDStateChangeRepeated(intendedChange byte, repeatCount int, minRepeatDelay time.Duration, timeout time.Duration) (changed *HIDLEDState,err error) {
func (kbd *HIDKeyboard) WaitLEDStateChangeRepeated(ctxIrq context.Context, intendedChange byte, repeatCount int, minRepeatDelay time.Duration, timeout time.Duration) (changed *HIDLEDState,err error) {
//register state change listener
l,err := kbd.LEDWatcher.RetrieveNewListener()
if err!= nil { return nil,err }
@ -454,6 +456,8 @@ func (kbd *HIDKeyboard) WaitLEDStateChangeRepeated(intendedChange byte, repeatCo
//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.ledWatcher.ctx.Done():
return nil, ErrAbort
case <-ctxIrq.Done():
return nil, ErrIrq
case <- time.After(remaining):
return nil, ErrTimeout
}

View File

@ -70,7 +70,7 @@ func TestLEDTriggers(hidCtl *hid.HIDController) {
//Test repeat trigger on any LED
fmt.Println("Waiting for any repeated LED state change (5 times frequently), wait timeout after 20 seconds...")
trigger, err := hidCtl.Keyboard.WaitLEDStateChangeRepeated(hid.MaskAny, 5, time.Millisecond*500, 20*time.Second)
trigger, err := hidCtl.Keyboard.WaitLEDStateChangeRepeated(context.Background(), hid.MaskAny, 5, time.Millisecond*500, 20*time.Second)
if err != nil {
fmt.Printf("Waiting aborted with error: %v\n", err)
} else {
@ -79,7 +79,7 @@ func TestLEDTriggers(hidCtl *hid.HIDController) {
//Test single trigger on any LED
fmt.Println("Waiting for any LED single state change, timeout after 15 seconds")
trigger, err = hidCtl.Keyboard.WaitLEDStateChange(hid.MaskAny, 15*time.Second)
trigger, err = hidCtl.Keyboard.WaitLEDStateChange(context.Background(),hid.MaskAny, 15*time.Second)
if err != nil {
fmt.Printf("Waiting aborted with error: %v\n", err)
} else {
@ -91,7 +91,7 @@ func TestLEDTriggers(hidCtl *hid.HIDController) {
//Test single trigger on NUMLOCK LED (ignore CAPSLOCK, SCROLLLOCK etc.)
fmt.Println("Waiting for NUMLOCK LED state change, timeout after 15 seconds")
trigger, err = hidCtl.Keyboard.WaitLEDStateChange(hid.MaskNumLock, 15*time.Second)
trigger, err = hidCtl.Keyboard.WaitLEDStateChange(context.Background(),hid.MaskNumLock, 15*time.Second)
if err != nil {
fmt.Printf("Waiting aborted with error: %v\n", err)
} else {
@ -100,7 +100,7 @@ func TestLEDTriggers(hidCtl *hid.HIDController) {
//Test single trigger on NUMLOCK LED (ignore CAPSLOCK, SCROLLLOCK etc.)
fmt.Println("Waiting for CAPSLOCK LED state change for 15 seconds")
trigger, err = hidCtl.Keyboard.WaitLEDStateChange(hid.MaskCapsLock, 15*time.Second)
trigger, err = hidCtl.Keyboard.WaitLEDStateChange(context.Background(), hid.MaskCapsLock, 15*time.Second)
if err != nil {
fmt.Printf("Waiting aborted with error: %v\n", err)
} else {
@ -111,7 +111,7 @@ func TestLEDTriggers(hidCtl *hid.HIDController) {
func TestMultiLEDTrigges(hidCtl *hid.HIDController, triggerMask byte) {
//Test repeat trigger on given LED
fmt.Printf("Waiting for repeated LED state change (5 times frequently) of mask %v, wait timeout after 20 seconds...\n", triggerMask)
trigger, err := hidCtl.Keyboard.WaitLEDStateChangeRepeated(triggerMask, 5, time.Millisecond*500, 20*time.Second)
trigger, err := hidCtl.Keyboard.WaitLEDStateChangeRepeated(context.Background(),triggerMask, 5, time.Millisecond*500, 20*time.Second)
if err != nil {
fmt.Printf("Waiting aborted with error: %v\n", err)
} else {
@ -332,7 +332,8 @@ func main() {
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');"
//script := "console.log('START ' + JID + ' on VM ' + VMID);delay(5000);console.log(JID + ' returned from 5s blocking delay');"
script := "console.log('START ' + JID + ' on VM ' + VMID);waitLEDRepeat(ANY,5000);console.log(JID + ' returned from 5s blocking delay');"
startTime := time.Now()
for i:=1; i<4; i++ {
job,err := hidCtl.StartScriptAsBackgroundJob(ctxT,script)