Skip to content

Commit d215cfd

Browse files
author
Tobias Deiminger
committed
Use a BlockingCollection to avoid busy loop in REST API threads
The former implementation had 5 threads permanently spinning fast (10ms sleep) while waiting for a REST connection to process. This causes higher load in general and it breaks systems where "Turn on PowerShell Script Block Logging policy" is enabled, because then each PS statement including Start-Sleep is logged - resulting in 500 event log entries per second. It's a suggested setting in some hardening guidelines. We can easily replace the Queue with a BlockingCollection backed by a ConcurrentQueue, which has the built-in feature to sleep until there are new items. Now the REST API threads consumes zero CPU time while waiting.
1 parent 90e80c8 commit d215cfd

File tree

4 files changed

+8
-17
lines changed

4 files changed

+8
-17
lines changed

doc/100-General/10-Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
1919
* [#480](https://github.com/Icinga/icinga-powershell-framework/pull/480) Fixes service locking during Icinga Agent upgrade and ensures errors on service management are caught and printed with internal error handling
2020
* [#483](https://github.com/Icinga/icinga-powershell-framework/issues/483) Fixes REST-Api SSL certificate lookup from the Icinga Agent, in case a custom hostname was used or in certain domain environments were domain is not matching DNS domain
2121
* [#490](https://github.com/Icinga/icinga-powershell-framework/pull/490) Fixes the command `Uninstall-IcingaComponent` for the `service` component which is not doing anything
22+
* [#497](https://github.com/Icinga/icinga-powershell-framework/pull/497) Fixes loop sleep for idle REST-Api threads by replacing them with [BlockingCollection](https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.blockingcollection-1?view=net-6.0) [ConcurrentQueue](https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentqueue-1?view=net-6.0)
2223

2324
### Enhancements
2425

lib/daemons/RestAPI/daemon/New-IcingaForWindowsRESTApi.psm1

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ function New-IcingaForWindowsRESTApi()
108108
continue;
109109
}
110110

111-
$Global:Icinga.Public.Daemons.RESTApi.ApiRequests.$NextRESTApiThreadId.Enqueue($Connection);
111+
$Global:Icinga.Public.Daemons.RESTApi.ApiRequests.$NextRESTApiThreadId.Add($Connection);
112112
} catch {
113113
Write-IcingaEventMessage -Namespace 'RESTApi' -EvenId 2050 -ExceptionObject $_;
114114
}

lib/daemons/RestAPI/daemon/Start-IcingaWindowsRESTApi.psm1

+3-2
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ function Start-IcingaWindowsRESTApi()
7575

7676
while ($ConcurrentThreads -gt 0) {
7777
$ConcurrentThreads = $ConcurrentThreads - 1;
78-
[System.Collections.Queue]$RESTThreadQueue = @();
79-
$Global:Icinga.Public.Daemons.RESTApi.ApiRequests.Add($ThreadId, [System.Collections.Queue]::Synchronized($RESTThreadQueue));
78+
$RESTThreadQueue = New-Object System.Collections.Concurrent.BlockingCollection[PSObject] `
79+
-ArgumentList (New-Object System.Collections.Concurrent.ConcurrentQueue[PSObject]);
80+
$Global:Icinga.Public.Daemons.RESTApi.ApiRequests.Add($ThreadId, $RESTThreadQueue);
8081
Start-IcingaForWindowsRESTThread -ThreadId $ThreadId -RequireAuth:$RequireAuth;
8182
$ThreadId += 1;
8283

lib/daemons/RestAPI/threads/New-IcingaForWindowsRESTThread.psm1

+3-14
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,8 @@ function New-IcingaForWindowsRESTThread()
1616
continue;
1717
}
1818

19-
if ($Global:Icinga.Public.Daemons.RESTApi.ApiRequests.$ThreadId.Count -eq 0) {
20-
Start-Sleep -Milliseconds 10;
21-
continue;
22-
}
23-
24-
$Connection = $Global:Icinga.Public.Daemons.RESTApi.ApiRequests.$ThreadId.Dequeue();
25-
26-
if ($null -eq $Connection) {
27-
Start-Sleep -Milliseconds 10;
28-
continue;
29-
}
19+
# block sleeping until content available
20+
$Connection = $Global:Icinga.Public.Daemons.RESTApi.ApiRequests.$ThreadId.Take();
3021

3122
# Read the received message from the stream by using our smart functions
3223
[string]$RestMessage = Read-IcingaTCPStream -Client $Connection.Client -Stream $Connection.Stream;
@@ -107,9 +98,7 @@ function New-IcingaForWindowsRESTThread()
10798

10899
# Finally close the clients connection as we are done here and
109100
# ensure this thread will close by simply leaving the function
110-
if ($null -ne $Connection) {
111-
Close-IcingaTCPConnection -Client $Connection.Client;
112-
}
101+
Close-IcingaTCPConnection -Client $Connection.Client;
113102

114103
# Force Icinga for Windows Garbage Collection
115104
Optimize-IcingaForWindowsMemory -ClearErrorStack;

0 commit comments

Comments
 (0)