The OSLIB jobs dispatcher was leaking job descriptors on the JOB_NULL control
path.
In both chJobDispatch() and chJobDispatchTimeout(), a fetched descriptor was
returned to the guarded pool only when jobfunc != NULL. When a null job was
used as the documented worker shutdown signal, the dispatcher returned
MSG_JOB_NULL but did not release the descriptor back to the free pool.
Impact:
- each null shutdown job permanently consumed one descriptor
- repeated worker shutdown cycles could exhaust the queue
- disposal-time checks did not detect the loss because the queue could still
appear idle
The fix:
- always return the fetched descriptor to the guarded pool, including the
MSG_JOB_NULL path
- add a regression test that posts null shutdown jobs, waits for the workers
to exit, then verifies:
- the guarded pool counter is back to JOBS_QUEUE_SIZE
- the mailbox has no pending jobs