Skip to content

fix: close event monitor race in bsdsocket emulation#1792

Merged
midwan merged 1 commit intoBlitterStudio:masterfrom
tbdye:fix/eventmask-race
Feb 16, 2026
Merged

fix: close event monitor race in bsdsocket emulation#1792
midwan merged 1 commit intoBlitterStudio:masterfrom
tbdye:fix/eventmask-race

Conversation

@tbdye
Copy link
Contributor

@tbdye tbdye commented Feb 16, 2026

Summary

Closes a race condition in the SO_EVENTMASK event monitor that caused intermittent spurious signal delivery after event mask teardown.

The event monitor thread runs select() concurrently with the Amiga emulation thread. When an application clears SO_EVENTMASK to 0 (tearing down event monitoring), there is a window where select() has already returned with a ready result but the monitor hasn't yet processed it. If the monitor locks the mutex before unregister_socket_events() runs, it fires a stale event via post_socket_event() — writing SET_* flags to ftable and queuing a signal via addtosigqueue(). The signal can then be delivered after the application has cleared and freed the signal bit, causing the next operation that allocates the same signal bit to see a spurious pending signal.

Three one-line changes close both vectors of the race:

  • post_socket_event(): verify the socket still has an active event mask (ftable[sd] & REP_ALL) before posting. If SO_EVENTMASK was already cleared to 0, the event is silently dropped.
  • SO_EVENTMASK=0 handler: clear pending SET_* flags from ftable[sd] after unregistering, preventing GetSocketEvents() from returning stale events if the fd number is later reused.
  • host_CloseSocket(): same SET_* cleanup after unregister_socket_events() for consistency.

Test plan

  • Build and deploy updated binary
  • Run bsdsocktest 0.2.3 signals category — test 81 (SO_EVENTMASK spurious event on idle socket) was the flaky test
  • 6 consecutive clean loopback runs: 127 passed, 0 failed, 0 known, 15 skipped
  • Test 81 passed on all 6 runs (previously failed intermittently)

🤖 Generated with Claude Code

The event monitor thread could deliver a stale signal or write stale
SET_* flags to ftable after the application cleared SO_EVENTMASK to 0.
This happened when select() returned for a socket just as the main
thread was tearing down event monitoring — the monitor would call
post_socket_event() before unregister_socket_events() took effect.

Three changes close both vectors of the race:

1. post_socket_event() now verifies the socket still has an active
   event mask (REP_ALL bits in ftable) before posting. If the mask
   was already cleared, the event is silently dropped.

2. The SO_EVENTMASK=0 handler clears pending SET_* flags from ftable
   after unregistering, preventing GetSocketEvents() from returning
   stale events if the fd number is later reused.

3. host_CloseSocket() gets the same SET_* cleanup for consistency.

Validated with bsdsocktest 0.2.3: 6 consecutive clean runs of the
signals category (test 81 previously failed intermittently).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tbdye tbdye requested a review from midwan as a code owner February 16, 2026 21:59
@midwan midwan merged commit df764b9 into BlitterStudio:master Feb 16, 2026
14 checks passed
@tbdye tbdye deleted the fix/eventmask-race branch February 16, 2026 22:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants