Changeset 60061 in vbox for trunk/src/VBox/Main
- Timestamp:
- Mar 16, 2016 2:47:01 PM (9 years ago)
- Location:
- trunk/src/VBox/Main
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Main/include/ClientWatcher.h
r48431 r60061 1 1 /* $Id$ */ 2 3 2 /** @file 4 *5 3 * VirtualBox API client session watcher 6 4 */ 7 5 8 6 /* 9 * Copyright (C) 2013 Oracle Corporation7 * Copyright (C) 2013-2016 Oracle Corporation 10 8 * 11 9 * This file is part of VirtualBox Open Source Edition (OSE), as … … 21 19 #define ____H_CLIENTWATCHER 22 20 21 23 22 #include <list> 24 23 #include <VBox/com/ptr.h> … … 30 29 # define CWUPDATEREQARG NULL 31 30 # define CWUPDATEREQTYPE HANDLE 31 # ifdef CW_WITH_BIRD_WATCHING 32 # define CW_MAX_CLIENTS _16K /**< Max number of clients we can watch (windows). */ 33 # ifndef DEBUG /* The debug version triggers worker thread code much much earlier. */ 34 # define CW_MAX_CLIENTS_PER_THREAD 63 /**< Max clients per watcher thread (windows). */ 35 # else 36 # define CW_MAX_CLIENTS_PER_THREAD 3 /**< Max clients per watcher thread (windows). */ 37 # endif 38 # define CW_MAX_HANDLES_PER_THREAD (CW_MAX_CLIENTS_PER_THREAD + 1) /**< Max handles per thread. */ 39 # endif /* CW_WITH_BIRD_WATCHING */ 32 40 #elif defined(RT_OS_OS2) 33 41 # define CWUPDATEREQARG NIL_RTSEMEVENT … … 70 78 ClientWatcher(); 71 79 72 static DECLCALLBACK(int) worker(RTTHREAD /* thread */, void *pvUser);80 static DECLCALLBACK(int) worker(RTTHREAD hThreadSelf, void *pvUser); 73 81 74 82 VirtualBox *mVirtualBox; … … 83 91 uint8_t mUpdateAdaptCtr; 84 92 #endif 93 #ifdef CW_WITH_BIRD_WATCHING 94 #ifdef RT_OS_WINDOWS 95 /** Indicate a real update request is pending. 96 * To avoid race conditions this must be set before mUpdateReq is signalled and 97 * read after resetting mUpdateReq. */ 98 volatile bool mfUpdateReq; 99 /** Set when the worker threads are supposed to shut down. */ 100 volatile bool mfTerminate; 101 /** Number of active subworkers. 102 * When decremented to 0, subworker zero is signalled. */ 103 uint32_t volatile mcActiveSubworkers; 104 /** Number of valid handles in mahWaitHandles. */ 105 uint32_t mcWaitHandles; 106 /** The wait interval (usually INFINITE). */ 107 uint32_t mcMsWait; 108 /** Per subworker data. Subworker 0 is the main worker and does not have a 109 * pReq pointer since. */ 110 struct PerSubworker 111 { 112 /** The wait result. */ 113 DWORD dwWait; 114 /** The subworker index. */ 115 uint32_t iSubworker; 116 /** The subworker thread handle. */ 117 RTTHREAD hThread; 118 /** Self pointer (for worker thread). */ 119 VirtualBox::ClientWatcher *pSelf; 120 } maSubworkers[(CW_MAX_CLIENTS + CW_MAX_CLIENTS_PER_THREAD - 1) / CW_MAX_CLIENTS_PER_THREAD]; 121 /** Wait handle array. The mUpdateReq manual reset event handle is inserted 122 * every 64 entries, first entry being 0. */ 123 HANDLE mahWaitHandles[CW_MAX_CLIENTS + (CW_MAX_CLIENTS + CW_MAX_CLIENTS_PER_THREAD - 1) / CW_MAX_CLIENTS_PER_THREAD]; 124 125 void subworkerWait(VirtualBox::ClientWatcher::PerSubworker *pSubworker, uint32_t cMsWait); 126 static DECLCALLBACK(int) subworkerThread(RTTHREAD hThreadSelf, void *pvUser); 127 void winResetHandleArray(uint32_t cProcHandles); 128 #endif 129 #endif /* CW_WITH_BIRD_WATCHING */ 85 130 }; 86 131 -
trunk/src/VBox/Main/src-server/ClientWatcher.cpp
r56958 r60061 1 1 /* $Id$ */ 2 2 /** @file 3 *4 3 * VirtualBox API client session crash watcher 5 4 */ 6 5 7 6 /* 8 * Copyright (C) 2006-201 4Oracle Corporation7 * Copyright (C) 2006-2016 Oracle Corporation 9 8 * 10 9 * This file is part of VirtualBox Open Source Edition (OSE), as … … 17 16 */ 18 17 18 #define LOG_GROUP LOG_GROUP_MAIN 19 19 #include <iprt/asm.h> 20 20 #include <iprt/assert.h> 21 #include <iprt/log.h>22 21 #include <iprt/semaphore.h> 23 22 #include <iprt/process.h> 24 23 24 #include <VBox/log.h> 25 25 #include <VBox/com/defs.h> 26 26 … … 85 85 { 86 86 #if defined(RT_OS_WINDOWS) 87 # ifdef CW_WITH_BIRD_WATCHING 88 /* Misc state. */ 89 mfTerminate = false; 90 mcMsWait = INFINITE; 91 mcActiveSubworkers = 0; 92 93 /* Update request. The UpdateReq event is also used to wake up subthreads. */ 94 mfUpdateReq = false; 95 mUpdateReq = ::CreateEvent(NULL /*pSecAttr*/, TRUE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pszName*/); 96 AssertRelease(mUpdateReq != NULL); 97 98 /* Initialize the handle array. */ 99 for (uint32_t i = 0; i < RT_ELEMENTS(mahWaitHandles); i++) 100 mahWaitHandles[i] = NULL; 101 for (uint32_t i = 0; i < RT_ELEMENTS(mahWaitHandles); i += CW_MAX_HANDLES_PER_THREAD) 102 mahWaitHandles[i] = mUpdateReq; 103 mcWaitHandles = 1; 104 105 # else 87 106 mUpdateReq = ::CreateEvent(NULL, FALSE, FALSE, NULL); 107 # endif 88 108 #elif defined(RT_OS_OS2) 89 109 RTSemEventCreate(&mUpdateReq); … … 117 137 { 118 138 AssertReturnVoid(mThread != NIL_RTTHREAD); 139 LogFlowFunc(("ping!\n")); 119 140 120 141 /* sent an update request */ 121 142 #if defined(RT_OS_WINDOWS) 143 #ifdef CW_WITH_BIRD_WATCHING 144 ASMAtomicWriteBool(&mfUpdateReq, true); 145 #endif 122 146 ::SetEvent(mUpdateReq); 123 147 #elif defined(RT_OS_OS2) … … 149 173 } 150 174 175 #ifdef CW_WITH_BIRD_WATCHING 176 177 /** 178 * Closes all the client process handles in mahWaitHandles. 179 * 180 * The array is divided into two ranges, first range are mutext handles of 181 * established sessions, the second range is zero or more process handles of 182 * spawning sessions. It's the latter that we close here, the former will just 183 * be NULLed out. 184 * 185 * @param cProcHandles The number of process handles. 186 */ 187 void VirtualBox::ClientWatcher::winResetHandleArray(uint32_t cProcHandles) 188 { 189 uint32_t idxHandle = mcWaitHandles; 190 Assert(cProcHandles < idxHandle); 191 Assert(idxHandle > 0); 192 193 /* Spawning process handles. */ 194 while (cProcHandles-- > 0 && idxHandle > 0) 195 { 196 idxHandle--; 197 if (idxHandle % CW_MAX_HANDLES_PER_THREAD) 198 { 199 Assert(mahWaitHandles[idxHandle] != mUpdateReq); 200 LogFlow(("UPDATE: closing %p\n", mahWaitHandles[idxHandle])); 201 CloseHandle(mahWaitHandles[idxHandle]); 202 mahWaitHandles[idxHandle] = NULL; 203 } 204 else 205 Assert(mahWaitHandles[idxHandle] == mUpdateReq); 206 } 207 208 /* Mutex handles (not to be closed). */ 209 while (idxHandle-- > 0) 210 if (idxHandle % CW_MAX_HANDLES_PER_THREAD) 211 { 212 Assert(mahWaitHandles[idxHandle] != mUpdateReq); 213 mahWaitHandles[idxHandle] = NULL; 214 } 215 else 216 Assert(mahWaitHandles[idxHandle] == mUpdateReq); 217 218 /* Reset the handle count. */ 219 mcWaitHandles = 1; 220 } 221 222 /** 223 * Does the waiting on a section of the handle array. 224 * 225 * @param pSubworker Pointer to the calling thread's data. 226 * @param cMsWait Number of milliseconds to wait. 227 */ 228 void VirtualBox::ClientWatcher::subworkerWait(VirtualBox::ClientWatcher::PerSubworker *pSubworker, uint32_t cMsWait) 229 { 230 /* 231 * Figure out what section to wait on and do the waiting. 232 */ 233 uint32_t idxHandle = pSubworker->iSubworker * CW_MAX_HANDLES_PER_THREAD; 234 uint32_t cHandles = CW_MAX_HANDLES_PER_THREAD; 235 if (idxHandle + cHandles > mcWaitHandles) 236 { 237 cHandles = mcWaitHandles - idxHandle; 238 AssertStmt(idxHandle < mcWaitHandles, cHandles = 1); 239 } 240 Assert(mahWaitHandles[idxHandle] == mUpdateReq); 241 242 DWORD dwWait = ::WaitForMultipleObjects(cHandles, 243 &mahWaitHandles[idxHandle], 244 FALSE /*fWaitAll*/, 245 cMsWait); 246 pSubworker->dwWait = dwWait; 247 248 /* 249 * If we didn't wake up because of the UpdateReq handle, signal it to make 250 * sure everyone else wakes up too. 251 */ 252 if (dwWait != WAIT_OBJECT_0) 253 { 254 BOOL fRc = SetEvent(mUpdateReq); 255 Assert(fRc); NOREF(fRc); 256 } 257 258 /* 259 * Last one signals the main thread. 260 */ 261 if (ASMAtomicDecU32(&mcActiveSubworkers) == 0) 262 { 263 int vrc = RTThreadUserSignal(maSubworkers[0].hThread); 264 AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserSignal -> %Rrc\n", vrc)); 265 } 266 267 } 268 151 269 /** 152 270 * Thread worker function that watches the termination of all client processes … … 154 272 */ 155 273 /*static*/ 156 DECLCALLBACK(int) VirtualBox::ClientWatcher::worker(RTTHREAD /* thread */, void *pvUser) 274 DECLCALLBACK(int) VirtualBox::ClientWatcher::subworkerThread(RTTHREAD hThreadSelf, void *pvUser) 275 { 276 VirtualBox::ClientWatcher::PerSubworker *pSubworker = (VirtualBox::ClientWatcher::PerSubworker *)pvUser; 277 VirtualBox::ClientWatcher *pThis = pSubworker->pSelf; 278 int vrc; 279 while (!pThis->mfTerminate) 280 { 281 /* Before we start waiting, reset the event semaphore. */ 282 vrc = RTThreadUserReset(pSubworker->hThread); 283 AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserReset [iSubworker=%#u] -> %Rrc", pSubworker->iSubworker, vrc)); 284 285 /* Do the job. */ 286 pThis->subworkerWait(pSubworker, pThis->mcMsWait); 287 288 /* Wait for the next job. */ 289 do 290 { 291 vrc = RTThreadUserWaitNoResume(hThreadSelf, RT_INDEFINITE_WAIT); 292 Assert(vrc == VINF_SUCCESS || vrc == VERR_INTERRUPTED); 293 } 294 while ( vrc != VINF_SUCCESS 295 && !pThis->mfTerminate); 296 } 297 return VINF_SUCCESS; 298 } 299 300 301 #endif /* CW_WITH_BIRD_WATCHING */ 302 303 /** 304 * Thread worker function that watches the termination of all client processes 305 * that have open sessions using IMachine::LockMachine() 306 */ 307 /*static*/ 308 DECLCALLBACK(int) VirtualBox::ClientWatcher::worker(RTTHREAD hThreadSelf, void *pvUser) 157 309 { 158 310 LogFlowFuncEnter(); 311 NOREF(hThreadSelf); 159 312 160 313 VirtualBox::ClientWatcher *that = (VirtualBox::ClientWatcher *)pvUser; … … 176 329 /// @todo (dmik) processes reaping! 177 330 331 #ifdef CW_WITH_BIRD_WATCHING 332 int vrc; 333 334 /* Initialize all the subworker data. */ 335 that->maSubworkers[0].hThread = hThreadSelf; 336 for (uint32_t iSubworker = 1; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++) 337 that->maSubworkers[iSubworker].hThread = NIL_RTTHREAD; 338 for (uint32_t iSubworker = 0; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++) 339 { 340 that->maSubworkers[iSubworker].pSelf = that; 341 that->maSubworkers[iSubworker].iSubworker = iSubworker; 342 } 343 #else 178 344 HANDLE handles[MAXIMUM_WAIT_OBJECTS]; 179 345 handles[0] = that->mUpdateReq; 346 #endif 180 347 181 348 do … … 192 359 autoCaller.release(); 193 360 361 #ifdef CW_WITH_BIRD_WATCHING 362 /* Kick of the waiting. */ 363 uint32_t const cSubworkers = (that->mcWaitHandles + CW_MAX_HANDLES_PER_THREAD - 1) / CW_MAX_HANDLES_PER_THREAD; 364 uint32_t const cMsWait = !fPidRace ? INFINITE : 500; 365 LogFlowFunc(("UPDATE: Waiting. %u handles, %u subworkers, %u ms wait\n", that->mcWaitHandles, cSubworkers, cMsWait)); 366 367 that->mcMsWait = cMsWait; 368 ASMAtomicWriteU32(&that->mcActiveSubworkers, cSubworkers); 369 RTThreadUserReset(hThreadSelf); 370 371 for (uint32_t iSubworker = 1; iSubworker < cSubworkers; iSubworker++) 372 { 373 if (that->maSubworkers[iSubworker].hThread != NIL_RTTHREAD) 374 { 375 vrc = RTThreadUserSignal(that->maSubworkers[iSubworker].hThread); 376 AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserSignal -> %Rrc\n", vrc)); 377 } 378 else 379 { 380 vrc = RTThreadCreateF(&that->maSubworkers[iSubworker].hThread, 381 VirtualBox::ClientWatcher::subworkerThread, &that->maSubworkers[iSubworker], 382 _128K, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "Watcher%u", iSubworker); 383 AssertLogRelMsgStmt(RT_SUCCESS(vrc), ("%Rrc iSubworker=%u\n", vrc, iSubworker), 384 that->maSubworkers[iSubworker].hThread = NIL_RTTHREAD); 385 } 386 if (RT_FAILURE(vrc)) 387 that->subworkerWait(&that->maSubworkers[iSubworker], 1); 388 } 389 390 /* Wait ourselves. */ 391 that->subworkerWait(&that->maSubworkers[0], cMsWait); 392 393 /* Make sure all waiters are done waiting. */ 394 BOOL fRc = SetEvent(that->mUpdateReq); 395 Assert(fRc); NOREF(fRc); 396 397 vrc = RTThreadUserWait(hThreadSelf, RT_INDEFINITE_WAIT); 398 AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserWait -> %Rrc\n", vrc)); 399 Assert(that->mcActiveSubworkers == 0); 400 401 /* Consume pending update request before proceeding with processing the wait results. */ 402 fRc = ResetEvent(that->mUpdateReq); 403 Assert(fRc); 404 405 bool update = ASMAtomicXchgBool(&that->mfUpdateReq, false); 406 if (update) 407 LogFlowFunc(("UPDATE: Update request pending\n")); 408 update |= fPidRace; 409 410 /* Process the wait results. */ 411 autoCaller.add(); 412 if (!autoCaller.isOk()) 413 break; 414 for (uint32_t iSubworker = 0; iSubworker < cSubworkers; iSubworker++) 415 { 416 DWORD dwWait = that->maSubworkers[iSubworker].dwWait; 417 LogFlowFunc(("UPDATE: subworker #%u: dwWait=%#x\n", iSubworker, dwWait)); 418 if ( (dwWait > WAIT_OBJECT_0 && dwWait < WAIT_OBJECT_0 + CW_MAX_HANDLES_PER_THREAD) 419 || (dwWait > WAIT_ABANDONED_0 && dwWait < WAIT_ABANDONED_0 + CW_MAX_HANDLES_PER_THREAD) ) 420 { 421 uint32_t idxHandle = iSubworker * CW_MAX_HANDLES_PER_THREAD; 422 if (dwWait > WAIT_OBJECT_0 && dwWait < WAIT_OBJECT_0 + CW_MAX_HANDLES_PER_THREAD) 423 idxHandle += dwWait - WAIT_OBJECT_0; 424 else 425 idxHandle += dwWait - WAIT_ABANDONED_0; 426 427 uint32_t const idxMachine = idxHandle - (iSubworker + 1); 428 if (idxMachine < cnt) 429 { 430 /* Machine mutex is released or abandond due to client process termination. */ 431 LogFlowFunc(("UPDATE: Calling i_checkForDeath on idxMachine=%u (idxHandle=%u) dwWait=%#x\n", 432 idxMachine, idxHandle, dwWait)); 433 (machines[idxMachine])->i_checkForDeath(); 434 } 435 else if (idxMachine < cnt + cntSpawned) 436 { 437 /* Spawned VM process has terminated normally. */ 438 Assert(dwWait < WAIT_ABANDONED_0); 439 LogFlowFunc(("UPDATE: Calling i_checkForSpawnFailure on idxMachine=%u/%u idxHandle=%u dwWait=%#x\n", 440 idxMachine, idxMachine - cnt, idxHandle, dwWait)); 441 (spawnedMachines[idxMachine - cnt])->i_checkForSpawnFailure(); 442 } 443 else 444 AssertFailed(); 445 update = true; 446 } 447 else 448 Assert(dwWait == WAIT_OBJECT_0 || dwWait == WAIT_TIMEOUT); 449 } 450 451 #else 194 452 DWORD rc = ::WaitForMultipleObjects((DWORD)(1 + cnt + cntSpawned), 195 453 handles, 196 454 FALSE, 197 455 !fPidRace ? INFINITE : 500); 456 LogFlowFunc(("UPDATE: dwWait=%#x\n", rc)); 198 457 199 458 /* Restore the caller before using VirtualBox. If it fails, this … … 213 472 { 214 473 /* machine mutex is released */ 474 LogFlowFunc(("UPDATE: Calling i_checkForDeath on #%u dwWait=%#x\n", rc - WAIT_OBJECT_0 - 1, rc)); 215 475 (machines[rc - WAIT_OBJECT_0 - 1])->i_checkForDeath(); 216 476 update = true; … … 219 479 { 220 480 /* machine mutex is abandoned due to client process termination */ 481 LogFlowFunc(("UPDATE: Calling i_checkForDeath on #%u dwWait=%#x\n", rc - WAIT_OBJECT_0 - 1, rc)); 221 482 (machines[rc - WAIT_ABANDONED_0 - 1])->i_checkForDeath(); 222 483 update = true; … … 225 486 { 226 487 /* spawned VM process has terminated (normally or abnormally) */ 488 LogFlowFunc(("UPDATE: Calling i_checkForSpawnFailure on #%u dwWait=%#x\n", rc - WAIT_OBJECT_0 - cnt - 1, rc)); 227 489 (spawnedMachines[rc - WAIT_OBJECT_0 - cnt - 1])-> 228 490 i_checkForSpawnFailure(); 229 491 update = true; 230 492 } 493 #endif 231 494 232 495 if (update) 233 496 { 497 LogFlowFunc(("UPDATE: Update pending (cnt=%u cntSpawned=%u)...\n", cnt, cntSpawned)); 498 234 499 /* close old process handles */ 500 #ifdef CW_WITH_BIRD_WATCHING 501 that->winResetHandleArray((uint32_t)cntSpawned); 502 #else 235 503 for (size_t i = 1 + cnt; i < 1 + cnt + cntSpawned; ++i) 236 504 CloseHandle(handles[i]); 505 #endif 237 506 238 507 // get reference to the machines list in VirtualBox … … 245 514 cnt = 0; 246 515 machines.clear(); 516 #ifdef CW_WITH_BIRD_WATCHING 517 uint32_t idxHandle = 0; 518 #endif 247 519 248 520 for (MachinesOList::iterator it = allMachines.begin(); … … 250 522 ++it) 251 523 { 524 #ifdef CW_WITH_BIRD_WATCHING 525 AssertMsgBreak(idxHandle < CW_MAX_CLIENTS, ("CW_MAX_CLIENTS reached")); 526 #else 252 527 /// @todo handle situations with more than 64 objects 253 528 AssertMsgBreak((1 + cnt) <= MAXIMUM_WAIT_OBJECTS, 254 529 ("MAXIMUM_WAIT_OBJECTS reached")); 530 #endif 255 531 256 532 ComObjPtr<SessionMachine> sm; … … 266 542 HANDLE ipcSem = ct->getToken(); 267 543 machines.push_back(sm); 544 #ifdef CW_WITH_BIRD_WATCHING 545 if (!(idxHandle % CW_MAX_HANDLES_PER_THREAD)) 546 idxHandle++; 547 that->mahWaitHandles[idxHandle++] = ipcSem; 548 #else 268 549 handles[1 + cnt] = ipcSem; 550 #endif 269 551 ++cnt; 270 552 } … … 284 566 ++it) 285 567 { 568 #ifdef CW_WITH_BIRD_WATCHING 569 AssertMsgBreak(idxHandle < CW_MAX_CLIENTS, ("CW_MAX_CLIENTS reached")); 570 #else 286 571 /// @todo handle situations with more than 64 objects 287 572 AssertMsgBreak((1 + cnt + cntSpawned) <= MAXIMUM_WAIT_OBJECTS, 288 573 ("MAXIMUM_WAIT_OBJECTS reached")); 574 #endif 289 575 290 576 if ((*it)->i_isSessionSpawning()) … … 301 587 { 302 588 spawnedMachines.push_back(*it); 589 #ifdef CW_WITH_BIRD_WATCHING 590 if (!(idxHandle % CW_MAX_HANDLES_PER_THREAD)) 591 idxHandle++; 592 that->mahWaitHandles[idxHandle++] = hProc; 593 #else 303 594 handles[1 + cnt + cntSpawned] = hProc; 595 #endif 304 596 ++cntSpawned; 305 597 } … … 313 605 LogFlowFunc(("UPDATE: spawned session count = %d\n", cntSpawned)); 314 606 607 #ifdef CW_WITH_BIRD_WATCHING 608 /* Update mcWaitHandles and make sure there is at least one handle to wait on. */ 609 that->mcWaitHandles = RT_MAX(idxHandle, 1); 610 #endif 611 315 612 // machines lock unwinds here 316 613 } 614 else 615 LogFlowFunc(("UPDATE: No update pending.\n")); 317 616 } 318 617 while (true); … … 320 619 while (0); 321 620 621 #ifdef CW_WITH_BIRD_WATCHING 622 /* Terminate subworker threads. */ 623 ASMAtomicWriteBool(&that->mfTerminate, true); 624 for (uint32_t iSubworker = 1; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++) 625 if (that->maSubworkers[iSubworker].hThread != NIL_RTTHREAD) 626 RTThreadUserSignal(that->maSubworkers[iSubworker].hThread); 627 for (uint32_t iSubworker = 1; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++) 628 if (that->maSubworkers[iSubworker].hThread != NIL_RTTHREAD) 629 { 630 vrc = RTThreadWait(that->maSubworkers[iSubworker].hThread, RT_MS_1MIN, NULL /*prc*/); 631 if (RT_SUCCESS(vrc)) 632 that->maSubworkers[iSubworker].hThread = NIL_RTTHREAD; 633 else 634 AssertLogRelMsgFailed(("RTThreadWait -> %Rrc\n", vrc)); 635 } 636 #endif 637 322 638 /* close old process handles */ 639 #ifdef CW_WITH_BIRD_WATCHING 640 that->winResetHandleArray((uint32_t)cntSpawned); 641 #else 323 642 for (size_t i = 1 + cnt; i < 1 + cnt + cntSpawned; ++i) 324 643 CloseHandle(handles[i]); 644 #endif 325 645 326 646 /* release sets of machines if any */
Note:
See TracChangeset
for help on using the changeset viewer.