Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
buildDebGolangWbgo defaultTargets: 'bullseye-armhf bullseye-arm64',
defaultWbGoSoBranch: 'feature/amd64-build-race',
Comment thread
sikmir marked this conversation as resolved.
defaultRunLintian: true
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ GOTEST ?= $(GO) test
GCFLAGS :=
LDFLAGS := -X main.version=`git describe --tags --always --dirty`
GO_FLAGS := -buildvcs=false
GO_TEST_FLAGS := -v -cover
GO_TEST_FLAGS := -v -cover -race

Comment thread
sikmir marked this conversation as resolved.
ifeq ($(DEBUG),)
LDFLAGS += -s -w
Expand Down
6 changes: 6 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
wb-rules (2.40.1) stable; urgency=medium

* Fix race condition on service stop

-- Nikolay Korotkiy <nikolay.korotkiy@wirenboard.com> Wed, 13 May 2026 17:00:00 +0400

wb-rules (2.40.0) stable; urgency=medium

* Move notify/alarms to modules
Expand Down
44 changes: 37 additions & 7 deletions wbrules/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ type RuleEngine struct {
syncQueueActive bool
syncQueue chan func()
syncQuitCh chan chan struct{}
mainLoopDone chan struct{}
mqttClient wbgong.MQTTClient // for service
driver wbgong.Driver
driverReadyCh chan struct{}
Expand Down Expand Up @@ -689,6 +690,7 @@ func NewRuleEngine(driver wbgong.Driver, mqtt wbgong.MQTTClient, options *RuleEn
syncQueue: make(chan func(), SYNC_QUEUE_LEN),
syncQueueActive: true,
syncQuitCh: make(chan chan struct{}, 1),
mainLoopDone: nil,
mqttClient: mqtt,
driver: driver,
driverReadyCh: nil,
Expand Down Expand Up @@ -983,6 +985,14 @@ func (engine *RuleEngine) driverEventHandler(event wbgong.DriverEvent) {
}

func (engine *RuleEngine) CallSync(thunk func()) {
engine.statusMtx.Lock()
syncQueueActive := engine.syncQueueActive
engine.statusMtx.Unlock()

if !syncQueueActive {
return
}
Comment on lines +1002 to +1006

if atomic.LoadUint32(&engine.debugEnabled) == ATOMIC_TRUE {
delay := time.NewTimer(ENGINE_CALLSYNC_TIMEOUT)
select {
Expand All @@ -999,7 +1009,11 @@ func (engine *RuleEngine) CallSync(thunk func()) {
}
Comment thread
Copilot marked this conversation as resolved.

func (engine *RuleEngine) MaybeCallSync(thunk func()) {
if engine.syncQueueActive {
engine.statusMtx.Lock()
syncQueueActive := engine.syncQueueActive
engine.statusMtx.Unlock()

if syncQueueActive {
engine.CallSync(thunk)
} else {
thunk()
Comment thread
sikmir marked this conversation as resolved.
Expand Down Expand Up @@ -1281,6 +1295,11 @@ func (engine *RuleEngine) setupCron() {
func (engine *RuleEngine) handleStop() {
wbgong.Debug.Printf("engine stopped")

if engine.cron != nil {
engine.cron.Stop()
engine.cron = nil
}

engine.timersMutex.Lock()
timerEntries := make([]*TimerEntry, 0, len(engine.timers))
for _, entry := range engine.timers {
Expand All @@ -1297,7 +1316,6 @@ func (engine *RuleEngine) handleStop() {
engine.readyCh = nil
engine.driverReadyCh = nil
Comment thread
sikmir marked this conversation as resolved.
engine.syncQueueActive = false
close(engine.syncQueue)
engine.statusMtx.Unlock()
}

Expand Down Expand Up @@ -1339,18 +1357,25 @@ func (engine *RuleEngine) updateDebugEnabled() {
}

func (engine *RuleEngine) Start() {
engine.statusMtx.Lock()
engine.readyCh = make(chan struct{})
engine.driverReadyCh = make(chan struct{}, 1)
engine.syncQueueActive = true
engine.mainLoopDone = make(chan struct{})
engine.statusMtx.Unlock()

engine.eventBuffer = NewEventBuffer()

engine.driver.OnDriverEvent(engine.driverEventHandler)
engine.driver.OnRetainReady(func(tx wbgong.DriverTx) {
engine.driverReadyCh <- struct{}{}
})
Comment thread
sikmir marked this conversation as resolved.
Comment thread
sikmir marked this conversation as resolved.
engine.syncQueueActive = true
atomic.StoreUint32(&engine.active, ENGINE_ACTIVE)

go engine.mainLoop()
go func() {
defer close(engine.mainLoopDone)
engine.mainLoop()
}()
go engine.syncLoop()
}

Expand All @@ -1365,13 +1390,18 @@ func (engine *RuleEngine) Stop() {

engine.eventBuffer.Close()

// wait for main loop shutdown sequence to finish
engine.statusMtx.Lock()
mainLoopDone := engine.mainLoopDone
engine.statusMtx.Unlock()
if mainLoopDone != nil {
<-mainLoopDone
}
Comment thread
sikmir marked this conversation as resolved.
Outdated

// stop sync loop
q := make(chan struct{})
engine.syncQuitCh <- q
<-q
Comment thread
sikmir marked this conversation as resolved.

// wait for main loop to release sync queue
<-engine.syncQueue
}

func (engine *RuleEngine) IsActive() bool {
Expand Down