Avoiding Default Semaphore Exhaustion from Custom Code<!-- /*NS Branding Styles*/ --> .ns-kb-css-body-editor-container { p { font-size: 12pt; font-family: Lato; color: var(--now-color--text-primary, #000000); } span { font-size: 12pt; font-family: Lato; color: var(--now-color--text-primary, #000000); } h2 { font-size: 24pt; font-family: Lato; color: var(--now-color--text-primary, black); } h3 { font-size: 18pt; font-family: Lato; color: var(--now-color--text-primary, black); } h4 { font-size: 14pt; font-family: Lato; color: var(--now-color--text-primary, black); } a { font-size: 12pt; font-family: Lato; color: var(--now-color--link-primary, #00718F); } a:hover { font-size: 12pt; color: var(--now-color--link-primary, #024F69); } a:target { font-size: 12pt; color: var(--now-color--link-primary, #032D42); } a:visited { font-size: 12pt; color: var(--now-color--link-primary, #00718f); } ul { font-size: 12pt; font-family: Lato; } li { font-size: 12pt; font-family: Lato; } img { display: ; max-width: ; width: ; height: ; } } This article explains why custom scripts can exhaust the Default semaphore pool, and how to write scripts that avoid it. Why this happens The Default semaphore pool handles most end-user and script transactions on your instance. A single script or transaction that runs long, or hangs, holds one of those threads until it completes, is cancelled, or times out. Because the pool is shared, a small number of long-running custom scripts can exhaust it, causing unrelated transactions to queue or fail with no obvious connection to the actual cause. Common custom-code patterns that cause this, and how to avoid them Synchronous outbound REST calls in script includes. A script include making a foreground REST call holds its semaphore for the full duration of that call. If the remote endpoint is slow or unresponsive, this can run up against the transaction time limit while holding the semaphore the whole time. Prefer an async integration pattern (a Scheduled Job, or a Flow Designer integration) instead of a foreground synchronous call.Unbounded business rule loops. A business rule that retries on an error condition (for example, looping on a role-check failure) without a bounded retry count or exit condition can run indefinitely. Always cap retry loops with a fixed maximum and a clear exit path.Ad-hoc bulk scripts via Scripts - Background (sys.scripts.do). Running a large, unbatched query or update directly against a high-volume table (e.g. incident, sc_request) as a single foreground script can hold a semaphore for hours. Batch the operation (chunk with GlideRecord.setLimit() or similar) and run it as a Scheduled Job instead of a foreground script.Scheduling heavy background jobs during update-set commit windows. Background cleanup or bulk jobs that run concurrently with update-set commits compound load on the same semaphore pool. Schedule heavy jobs outside your regular update-set commit windows where possible. Self-diagnosing a stuck semaphore: If you suspect a custom script is holding a semaphore, check Active Transactions (User Administration > Active Transactions for the current node, or System Diagnostics > Active Transactions (All Nodes) for all nodes) to find the long-running transaction and, if needed, kill it. This is intended for background processes like scheduled jobs and script includes (not user-initiated transactions) so confirm what you're looking at before killing anything. For a historical view, System Logs > Transactions shows SQL time, business rule time, and business rule count for each completed transaction, which can help you identify which script or business rule is the actual offender. If you want to confirm the Default semaphore pool is actually under pressure before digging further, see Semaphores performance metrics for how to read the Default Semaphore Mean and Qdepth metrics. Known platform-side cause (separate from custom code): If you're on Zurich and using cross-experience/interoperable routes, a known platform issue can also cause stuck Default semaphores independent of your own code. See KB2901692 for the workaround and more details. A note on scope: These practices reduce the risk of a custom script exhausting the Default semaphore pool, they don't guarantee it can't happen. A script with a genuine defect (for example, an infinite loop) can still hold a semaphore regardless of these precautions.