forked from k-space/godoor
Handle keep_open_until: cancel, periodic poll, thread safety
- cancelKeepOpenDoor() closes the door when /allowed returns null - background goroutine polls /allowed every 15s (KDOORPI_ALLOWED_POLL_INTERVAL) - keepDoorOpenLock mutex guards shared hold state - skip timer rebuild when the hold is unchanged (anti-thrash)
This commit is contained in:
76
keepopen.go
76
keepopen.go
@@ -3,10 +3,28 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var keepDoorOpenLock sync.Mutex
|
||||
|
||||
// keepDoorOpenGen identifies the currently armed hold timer. It is bumped (under
|
||||
// keepDoorOpenLock) every time a new timer is installed so that a superseded
|
||||
// timer's callback can recognise it is stale and do nothing.
|
||||
var keepDoorOpenGen uint64
|
||||
|
||||
func updateKeepOpenDoor(newKeepOpenTime time.Time) {
|
||||
keepDoorOpenLock.Lock()
|
||||
defer keepDoorOpenLock.Unlock()
|
||||
|
||||
// Hold unchanged since the last poll: keep the existing timer rather than
|
||||
// rebuilding it every poll, which would float the close time forward by up
|
||||
// to one poll interval and re-pulse OpenDoor needlessly.
|
||||
if keepDoorOpen.timer != nil && newKeepOpenTime.Equal(keepDoorOpen.until) {
|
||||
return
|
||||
}
|
||||
|
||||
// is there one active?
|
||||
if keepDoorOpen.timer != nil {
|
||||
keepDoorOpen.timer.Stop()
|
||||
@@ -15,19 +33,65 @@ func updateKeepOpenDoor(newKeepOpenTime time.Time) {
|
||||
|
||||
if newKeepOpenTime.After(time.Now()) {
|
||||
log.Printf("Keeping door open until %v", newKeepOpenTime)
|
||||
OpenDoor(wiegand)
|
||||
timer := time.AfterFunc(time.Until(newKeepOpenTime), handleKeepDoorOpenCloseCleanup)
|
||||
if err := OpenDoor(wiegand); err != nil {
|
||||
// Don't commit the hold if the relay didn't actually open: leaving
|
||||
// keepDoorOpen empty (timer nil) means the next poll retries instead
|
||||
// of latching the Equal early-return on a door that never opened.
|
||||
log.Printf("ERROR opening door for hold: %v", err)
|
||||
return
|
||||
}
|
||||
keepDoorOpenGen++
|
||||
gen := keepDoorOpenGen
|
||||
timer := time.AfterFunc(time.Until(newKeepOpenTime), func() {
|
||||
handleKeepDoorOpenCloseCleanup(gen)
|
||||
})
|
||||
keepDoorOpen = KeepDoorOpen{
|
||||
timer: timer,
|
||||
until: newKeepOpenTime,
|
||||
}
|
||||
} else {
|
||||
CloseDoor(wiegand)
|
||||
if err := CloseDoor(wiegand); err != nil {
|
||||
log.Printf("ERROR closing door: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleKeepDoorOpenCloseCleanup() {
|
||||
fmt.Println("Keep door open time is reached!")
|
||||
CloseDoor(wiegand)
|
||||
func cancelKeepOpenDoor() {
|
||||
keepDoorOpenLock.Lock()
|
||||
defer keepDoorOpenLock.Unlock()
|
||||
|
||||
if keepDoorOpen.timer == nil {
|
||||
return
|
||||
}
|
||||
keepDoorOpen.timer.Stop()
|
||||
if err := CloseDoor(wiegand); err != nil {
|
||||
// Keep keepDoorOpen non-nil so the next poll's cancel retries the close;
|
||||
// clearing it now would strand the door OPEN with no retry path.
|
||||
log.Printf("ERROR closing door on hold cancel: %v", err)
|
||||
return
|
||||
}
|
||||
keepDoorOpen = KeepDoorOpen{}
|
||||
}
|
||||
|
||||
func handleKeepDoorOpenCloseCleanup(gen uint64) {
|
||||
keepDoorOpenLock.Lock()
|
||||
defer keepDoorOpenLock.Unlock()
|
||||
|
||||
// Timer.Stop() can return after this callback has already started and is
|
||||
// blocked here on the lock; by the time we acquire it, updateKeepOpenDoor
|
||||
// may have installed a newer hold. Only act if we are still the current
|
||||
// generation, otherwise we would close the door and clear a live hold.
|
||||
if gen != keepDoorOpenGen {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Keep door open time is reached!")
|
||||
if err := CloseDoor(wiegand); err != nil {
|
||||
// Leave keepDoorOpen intact so the next poll (which will see
|
||||
// keep_open_until=null and call cancelKeepOpenDoor) retries the close
|
||||
// instead of stranding the door OPEN.
|
||||
log.Printf("ERROR closing door at hold expiry: %v", err)
|
||||
return
|
||||
}
|
||||
keepDoorOpen = KeepDoorOpen{}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user