Commit db5eca3
authored
Fix race condition in LoggingService.ProcessLoggingEvent after shutdown (#13450)
## Summary
Fix a race condition crash (access violation 0xC0000005 /
NullReferenceException) in `LoggingService.ProcessLoggingEvent` when the
method is called after the logging service has been shut down.
## Problem
`ProcessLoggingEvent` directly accesses `_eventQueue`, `_dequeueEvent`,
and `_enqueueEvent` without any shutdown guard. During
`ShutdownComponent()`, `CleanLoggingEventProcessing()` disposes and
nullifies all of these fields. If an external callback (e.g.,
`Process.Exited` from a `ProjectCachePlugin`) attempts to log a message
after shutdown has completed, the null field access causes an
unrecoverable crash.
### Reproduction Scenario
1. A build runs with a `ProjectCachePlugin`
2. The build completes and `LoggingService.ShutdownComponent()` is
called
3. The cache plugin's external process exits, triggering
`Process.OnExited` on a ThreadPool thread
4. The callback logs "Project cache service process exited" via
`LoggingServiceToPluginLoggerAdapter.LogMessage`
5. This calls `ProcessLoggingEvent()` which accesses `_eventQueue.Count`
→ **null reference crash**
### Debugger Evidence
```
_serviceState = Shutdown
_eventQueue = null
_dequeueEvent = null
_enqueueEvent = null
_emptyQueueEvent = null
_loggingEventProcessingCancellation = null
buildEvent.Message = "Project cache service process exited"
Call: System.Diagnostics.Process.OnExited -> ProjectCachePlugin.dll -> LoggingServiceToPluginLoggerAdapter.LogMessage
```
## Root Cause
`ProcessLoggingEvent` lacked two protections that already exist
elsewhere in the codebase:
1. **Shutdown state guard** - Other public methods (`RegisterLogger`,
`RegisterDistributedLogger`, `InitializeNodeLoggers`,
`InitializeComponent`) all check `_serviceState !=
LoggingServiceState.Shutdown`. `ProcessLoggingEvent` did not.
2. **Local field capture** - The `LoggingEventProc` queue pump thread
already captures `_eventQueue`, `_dequeueEvent`, `_emptyQueueEvent`, and
`_enqueueEvent` into local variables to prevent races with
`CleanLoggingEventProcessing()`. `ProcessLoggingEvent` accessed the
fields directly.
## Fix
Three layers of defense, all following patterns already established in
the same file:
1. **Early return** when `_serviceState == LoggingServiceState.Shutdown`
- silently drops the event, since the service is no longer operational.
2. **Capture fields to locals** before use - prevents null dereference
if `CleanLoggingEventProcessing()` runs concurrently between the state
check and the field reads.
3. **Null-check the captured locals** - second defense against the
TOCTOU race between step 1 and step 2.
## Risk
**Low.** The fix only adds early-exit guards - no behavioral change for
the normal (non-shutdown) code path. The patterns used are already
established in the same file.1 parent 5065de5 commit db5eca3
2 files changed
Lines changed: 151 additions & 6 deletions
File tree
- src
- Build.UnitTests/BackEnd
- Build/BackEnd/Components/Logging
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1075 | 1075 | | |
1076 | 1076 | | |
1077 | 1077 | | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
| 1125 | + | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
| 1131 | + | |
| 1132 | + | |
| 1133 | + | |
| 1134 | + | |
| 1135 | + | |
| 1136 | + | |
| 1137 | + | |
| 1138 | + | |
| 1139 | + | |
| 1140 | + | |
| 1141 | + | |
| 1142 | + | |
| 1143 | + | |
| 1144 | + | |
| 1145 | + | |
| 1146 | + | |
| 1147 | + | |
| 1148 | + | |
| 1149 | + | |
| 1150 | + | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
1078 | 1193 | | |
1079 | 1194 | | |
1080 | 1195 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1294 | 1294 | | |
1295 | 1295 | | |
1296 | 1296 | | |
| 1297 | + | |
| 1298 | + | |
| 1299 | + | |
| 1300 | + | |
| 1301 | + | |
| 1302 | + | |
| 1303 | + | |
| 1304 | + | |
1297 | 1305 | | |
1298 | 1306 | | |
1299 | 1307 | | |
1300 | | - | |
1301 | | - | |
| 1308 | + | |
| 1309 | + | |
| 1310 | + | |
| 1311 | + | |
| 1312 | + | |
| 1313 | + | |
| 1314 | + | |
| 1315 | + | |
| 1316 | + | |
1302 | 1317 | | |
1303 | | - | |
1304 | | - | |
| 1318 | + | |
1305 | 1319 | | |
1306 | 1320 | | |
1307 | | - | |
1308 | | - | |
| 1321 | + | |
| 1322 | + | |
| 1323 | + | |
| 1324 | + | |
| 1325 | + | |
| 1326 | + | |
| 1327 | + | |
| 1328 | + | |
| 1329 | + | |
| 1330 | + | |
| 1331 | + | |
| 1332 | + | |
| 1333 | + | |
| 1334 | + | |
| 1335 | + | |
| 1336 | + | |
| 1337 | + | |
| 1338 | + | |
1309 | 1339 | | |
1310 | 1340 | | |
1311 | 1341 | | |
| |||
0 commit comments