| OLD | NEW |
| (Empty) | |
| 1 #include <cstdio> |
| 2 |
| 3 #include <vector> |
| 4 |
| 5 #include "base/command_line.h" |
| 6 #include "base/process/launch.h" |
| 7 #include "base/process/process.h" |
| 8 #include "base/process/process_metrics.h" |
| 9 #include "base/threading/platform_thread.h" |
| 10 |
| 11 namespace { |
| 12 using ProcessList = std::vector<base::Process*>; |
| 13 |
| 14 void SleepForever() { |
| 15 for (;;) { |
| 16 base::PlatformThread::Sleep(base::TimeDelta::FromMinutes(1)); |
| 17 } |
| 18 } |
| 19 |
| 20 void LoadDll() {} |
| 21 |
| 22 void CreateSharedMemoryButDontShare() {} |
| 23 |
| 24 void ShareMeSomeMemory(int num_regions, ProcessList process_list) { |
| 25 for (base::Process* process : process_list) { |
| 26 CHECK(process); |
| 27 } |
| 28 } |
| 29 |
| 30 void ShareMeSomeCOWsAndTouchThem(int num_regions, ProcessList process_list) { |
| 31 for (base::Process* process : process_list) { |
| 32 CHECK(process); |
| 33 } |
| 34 } |
| 35 |
| 36 void ForceSwap() { |
| 37 char* leak = new char[12345]; |
| 38 leak = nullptr; |
| 39 } |
| 40 |
| 41 struct SavedStats { |
| 42 size_t private_bytes; |
| 43 size_t shared_bytes; |
| 44 uint64_t pss_bytes; |
| 45 base::WorkingSetKBytes working_set; |
| 46 base::CommittedKBytes committed; |
| 47 }; |
| 48 |
| 49 void SaveStats(SavedStats* saved_stats, base::Process* process) { |
| 50 std::unique_ptr<base::ProcessMetrics> metrics = |
| 51 base::ProcessMetrics::CreateProcessMetrics(process->Handle()); |
| 52 |
| 53 CHECK(metrics->GetMemoryBytes(&(saved_stats->private_bytes), &(saved_stats->sh
ared_bytes))); |
| 54 CHECK(metrics->GetProportionalSetSizeBytes(&(saved_stats->pss_bytes))); |
| 55 CHECK(metrics->GetWorkingSetKBytes(&saved_stats->working_set)); |
| 56 metrics->GetCommittedKBytes(&saved_stats->committed); |
| 57 } |
| 58 |
| 59 void DumpDiff(SavedStats* first, SavedStats* second) { |
| 60 LOG(ERROR) << " - private kbytes: " << ((int64_t)second->private_bytes - (int
64_t)first->private_bytes) / 1024; |
| 61 LOG(ERROR) << " - shared kbytes: " << ((int64_t)second->shared_bytes - (int64
_t)first->shared_bytes) / 1024; |
| 62 LOG(ERROR) << " - pss kbytes: " << ((int64_t)second->pss_bytes - (int64_t)fir
st->pss_bytes) / 1024; |
| 63 LOG(ERROR) << " - workingset private kb: " << ((int64_t)second->working_set.p
riv - (int64_t)first->working_set.priv); |
| 64 LOG(ERROR) << " - workingset mapped kb: " << ((int64_t)second->working_set.sh
areable - (int64_t)first->working_set.shareable); |
| 65 LOG(ERROR) << " - workingset image kb: " << ((int64_t)second->working_set.sha
red - (int64_t)first->working_set.shared); |
| 66 LOG(ERROR) << " - committed private kb: " << ((int64_t)second->committed.priv
- (int64_t)first->committed.priv); |
| 67 LOG(ERROR) << " - committed mapped kb: " << ((int64_t)second->committed.mappe
d - (int64_t)first->committed.mapped); |
| 68 LOG(ERROR) << " - committed image kb: " << ((int64_t)second->committed.image
- (int64_t)first->committed.image); |
| 69 LOG(ERROR) << std::endl << std::endl; |
| 70 } |
| 71 |
| 72 } // namespace |
| 73 |
| 74 void malloc1() { |
| 75 LOG(ERROR) << "malloc 16MB, and touch every page"; |
| 76 base::Process process(base::Process::Current()); |
| 77 SavedStats first, second; |
| 78 SaveStats(&first, &process); |
| 79 |
| 80 char*a = (char*)malloc(16 * 1024 * 1024); |
| 81 for (int i = 0; i < 16 * 1024 * 1024; ++i) { |
| 82 a[i] = 'a'; |
| 83 } |
| 84 SaveStats(&second, &process); |
| 85 |
| 86 DumpDiff(&first, &second); |
| 87 } |
| 88 |
| 89 void malloc2() { |
| 90 LOG(ERROR) << "malloc 16MB, but only touch every 16th page"; |
| 91 base::Process process(base::Process::Current()); |
| 92 SavedStats first, second; |
| 93 SaveStats(&first, &process); |
| 94 |
| 95 char*a = (char*)malloc(16 * 1024 * 1024); |
| 96 for(int i = 0; i < 16 * 1024 * 1024 / (16 * 4096); ++i) { |
| 97 a[i * 16 * 4096] = 'a'; |
| 98 } |
| 99 SaveStats(&second, &process); |
| 100 |
| 101 DumpDiff(&first, &second); |
| 102 } |
| 103 |
| 104 void malloc3() { |
| 105 LOG(ERROR) << "malloc 16MB, touch every page, then free"; |
| 106 base::Process process(base::Process::Current()); |
| 107 SavedStats first, second; |
| 108 SaveStats(&first, &process); |
| 109 |
| 110 char*a = (char*)malloc(16 * 1024 * 1024); |
| 111 for (int i = 0; i < 16 * 1024 * 1024; ++i) { |
| 112 a[i] = 'a'; |
| 113 } |
| 114 free(a); |
| 115 SaveStats(&second, &process); |
| 116 |
| 117 DumpDiff(&first, &second); |
| 118 } |
| 119 |
| 120 void malloc4() { |
| 121 LOG(ERROR) << "malloc 16MB in 1024 chunks, touch every page, then free every c
hunk that isn't a multiple of 16"; |
| 122 LOG(ERROR) << "I expect resident memory to hit ~4MB, but instead it stays at 1
6MB! What's going on?"; |
| 123 base::Process process(base::Process::Current()); |
| 124 SavedStats first, second; |
| 125 std::vector<char*> chunks; |
| 126 chunks.resize(16 * 1024); |
| 127 |
| 128 SaveStats(&first, &process); |
| 129 for (int i = 0; i < 16 * 1024; ++i) { |
| 130 chunks[i] = (char*)malloc(1024); |
| 131 chunks[i][0] = 'a'; |
| 132 } |
| 133 |
| 134 for (int i = 0; i < 16 * 1024; ++i) { |
| 135 if (i % 16 != 0) |
| 136 free(chunks[i]); |
| 137 } |
| 138 SaveStats(&second, &process); |
| 139 |
| 140 DumpDiff(&first, &second); |
| 141 } |
| 142 |
| 143 void anonymous_file_mapping1() { |
| 144 LOG(ERROR) << "create 16MB anonymous file mapping, and touch every page"; |
| 145 base::Process process(base::Process::Current()); |
| 146 SavedStats first, second; |
| 147 SaveStats(&first, &process); |
| 148 HANDLE h = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, |
| 149 16 * 1024 * 1024, NULL); |
| 150 char*a = (char*) MapViewOfFile(h, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 16 * 10
24 *1024); |
| 151 for (int i = 0; i < 16 * 1024 * 1024; ++i) |
| 152 a[i] = 'a'; |
| 153 SaveStats(&second, &process); |
| 154 DumpDiff(&first, &second); |
| 155 } |
| 156 |
| 157 void anonymous_file_mapping2() { |
| 158 LOG(ERROR) << "create 16MB anonymous file mapping"; |
| 159 base::Process process(base::Process::Current()); |
| 160 SavedStats first, second; |
| 161 SaveStats(&first, &process); |
| 162 CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, |
| 163 16 * 1024 * 1024, NULL); |
| 164 SaveStats(&second, &process); |
| 165 DumpDiff(&first, &second); |
| 166 } |
| 167 void anonymous_file_mapping3() { |
| 168 LOG(ERROR) << "create and map 16MB anonymous file mapping"; |
| 169 base::Process process(base::Process::Current()); |
| 170 SavedStats first, second; |
| 171 SaveStats(&first, &process); |
| 172 HANDLE h = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, |
| 173 16 * 1024 * 1024, NULL); |
| 174 MapViewOfFile(h, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 16 * 1024 *1024); |
| 175 SaveStats(&second, &process); |
| 176 DumpDiff(&first, &second); |
| 177 } |
| 178 |
| 179 void virtual_alloc1() { |
| 180 LOG(ERROR) << "virtual_alloc 16MB"; |
| 181 base::Process process(base::Process::Current()); |
| 182 SavedStats first, second; |
| 183 SaveStats(&first, &process); |
| 184 |
| 185 VirtualAlloc(NULL, 16 * 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)
; |
| 186 SaveStats(&second, &process); |
| 187 |
| 188 DumpDiff(&first, &second); |
| 189 } |
| 190 |
| 191 void virtual_alloc2() { |
| 192 LOG(ERROR) << "virtual_alloc 16MB, then write to 8MB of pages"; |
| 193 base::Process process(base::Process::Current()); |
| 194 SavedStats first, second; |
| 195 SaveStats(&first, &process); |
| 196 |
| 197 char* a = (char*)VirtualAlloc(NULL, 16 * 1024 * 1024, MEM_COMMIT | MEM_RESERVE
, PAGE_READWRITE); |
| 198 for (int i = 0; i < 8 * 1024 * 1024; ++i) { |
| 199 a[i] = 'a'; |
| 200 } |
| 201 SaveStats(&second, &process); |
| 202 |
| 203 DumpDiff(&first, &second); |
| 204 } |
| 205 |
| 206 void virtual_alloc3() { |
| 207 LOG(ERROR) << "virtual_alloc 16MB, then write to 8MB of pages, then decommits
4MB of the written pages"; |
| 208 base::Process process(base::Process::Current()); |
| 209 SavedStats first, second; |
| 210 SaveStats(&first, &process); |
| 211 |
| 212 char* a = (char*)VirtualAlloc(NULL, 16 * 1024 * 1024, MEM_COMMIT | MEM_RESERVE
, PAGE_READWRITE); |
| 213 for (int i = 0; i < 8 * 1024 * 1024; ++i) { |
| 214 a[i] = 'a'; |
| 215 } |
| 216 VirtualFree(a, 4 * 1024 * 1024, MEM_DECOMMIT); |
| 217 SaveStats(&second, &process); |
| 218 |
| 219 DumpDiff(&first, &second); |
| 220 } |
| 221 |
| 222 void virtual_alloc4() { |
| 223 LOG(ERROR) << "virtual_alloc 16MB, then write to 8MB of pages, then reset 4MB
of the written pages"; |
| 224 base::Process process(base::Process::Current()); |
| 225 SavedStats first, second; |
| 226 SaveStats(&first, &process); |
| 227 |
| 228 char* a = (char*)VirtualAlloc(NULL, 16 * 1024 * 1024, MEM_COMMIT | MEM_RESERVE
, PAGE_READWRITE); |
| 229 for (int i = 0; i < 8 * 1024 * 1024; ++i) { |
| 230 a[i] = 'a'; |
| 231 } |
| 232 VirtualAlloc(a, 4 * 1024 * 1024, MEM_RESET, PAGE_NOACCESS); |
| 233 SaveStats(&second, &process); |
| 234 |
| 235 DumpDiff(&first, &second); |
| 236 } |
| 237 |
| 238 void virtual_alloc5() { |
| 239 LOG(ERROR) << "virtual_alloc 16MB, then write to 8MB of pages, then discard 4M
B of the written pages"; |
| 240 base::Process process(base::Process::Current()); |
| 241 SavedStats first, second; |
| 242 SaveStats(&first, &process); |
| 243 |
| 244 char* a = (char*)VirtualAlloc(NULL, 16 * 1024 * 1024, MEM_COMMIT | MEM_RESERVE
, PAGE_READWRITE); |
| 245 for (int i = 0; i < 8 * 1024 * 1024; ++i) { |
| 246 a[i] = 'a'; |
| 247 } |
| 248 using DiscardVirtualMemoryFunction = |
| 249 DWORD(WINAPI*)(PVOID virtualAddress, SIZE_T size); |
| 250 DiscardVirtualMemoryFunction discard_virtual_memory = |
| 251 reinterpret_cast<DiscardVirtualMemoryFunction>(GetProcAddress( |
| 252 GetModuleHandle(L"Kernel32.dll"), "DiscardVirtualMemory")); |
| 253 discard_virtual_memory(a, 4 * 1024 * 1024); |
| 254 SaveStats(&second, &process); |
| 255 |
| 256 DumpDiff(&first, &second); |
| 257 } |
| 258 void heap_alloc1() { |
| 259 LOG(ERROR) << "HeapAlloc 16MB"; |
| 260 base::Process process(base::Process::Current()); |
| 261 SavedStats first, second; |
| 262 SaveStats(&first, &process); |
| 263 |
| 264 HeapAlloc(GetProcessHeap(), 0, 16 * 1024 * 1024); |
| 265 SaveStats(&second, &process); |
| 266 |
| 267 DumpDiff(&first, &second); |
| 268 } |
| 269 void heap_alloc2() { |
| 270 LOG(ERROR) << "HeapAlloc 16MB, and touch 8MB"; |
| 271 base::Process process(base::Process::Current()); |
| 272 SavedStats first, second; |
| 273 SaveStats(&first, &process); |
| 274 |
| 275 char* a = (char*)HeapAlloc(GetProcessHeap(), 0, 16 * 1024 * 1024); |
| 276 for (int i = 0; i < 8 * 1024 * 1024; ++i) { |
| 277 a[i] = 'a'; |
| 278 } |
| 279 SaveStats(&second, &process); |
| 280 |
| 281 DumpDiff(&first, &second); |
| 282 } |
| 283 void heap_alloc3() { |
| 284 LOG(ERROR) << "HeapAlloc 16MB, and touch 8MB, then decommit 4MB"; |
| 285 base::Process process(base::Process::Current()); |
| 286 SavedStats first, second; |
| 287 SaveStats(&first, &process); |
| 288 |
| 289 char* a = (char*)HeapAlloc(GetProcessHeap(), 0, 16 * 1024 * 1024); |
| 290 for (int i = 0; i < 8 * 1024 * 1024; ++i) { |
| 291 a[i] = 'a'; |
| 292 } |
| 293 VirtualFree(a, 4 * 1024 * 1024, MEM_DECOMMIT); |
| 294 SaveStats(&second, &process); |
| 295 |
| 296 DumpDiff(&first, &second); |
| 297 } |
| 298 |
| 299 void leak_1gb() { |
| 300 LOG(ERROR) << "leak 1gb"; |
| 301 base::Process process(base::Process::Current()); |
| 302 SavedStats first, second; |
| 303 SaveStats(&first, &process); |
| 304 char* a = (char*)malloc(1024 * 1024 * 1024); |
| 305 for (int i = 0; i < 1024 * 1024 * 1024 / 4096; ++i) |
| 306 a[i * 4096] = 'a'; |
| 307 SaveStats(&second, &process); |
| 308 DumpDiff(&first, &second); |
| 309 } |
| 310 |
| 311 void swap1() { |
| 312 LOG(ERROR) << "This will crash your machine. Are you sure you want to continue
(y)?"; |
| 313 char dummy; |
| 314 scanf("%c", &dummy); |
| 315 if (dummy != 'y') |
| 316 return; |
| 317 |
| 318 LOG(ERROR) << "malloc 256MB, touch every page, then spawn children that consum
e 64GB of memory"; |
| 319 base::Process process(base::Process::Current()); |
| 320 SavedStats first, second; |
| 321 SaveStats(&first, &process); |
| 322 |
| 323 char*a = (char*)malloc(256 * 1024 * 1024); |
| 324 for (int i = 0; i < 256 * 1024 * 1024 / 4096; ++i) { |
| 325 a[i * 4096] = 'a'; |
| 326 } |
| 327 for (int i = 0; i < 64; ++i) { |
| 328 base::LaunchOptions options; |
| 329 base::CommandLine child_command(*base::CommandLine::ForCurrentProcess()); |
| 330 child_command.AppendSwitch("leak_1gb"); |
| 331 base::LaunchProcess(child_command, options); |
| 332 } |
| 333 |
| 334 // Wait 100 seconds for memory to blow up. |
| 335 base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(100)); |
| 336 SaveStats(&second, &process); |
| 337 DumpDiff(&first, &second); |
| 338 } |
| 339 |
| 340 void swap2() { |
| 341 LOG(ERROR) << "Spawns 256 process, each of which commits 1GB with VirtualAlloc
but only makes 4MB of pages resident."; |
| 342 for (int i = 0; i < 256; ++i) { |
| 343 base::LaunchOptions options; |
| 344 base::CommandLine child_command(*base::CommandLine::ForCurrentProcess()); |
| 345 child_command.AppendSwitch("swap2_child"); |
| 346 base::LaunchProcess(child_command, options); |
| 347 } |
| 348 |
| 349 // Wait 3 seconds for memory to blow up. |
| 350 base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(3)); |
| 351 |
| 352 LOG(ERROR) << "try to allocate 1GB using malloc"; |
| 353 char*b = (char*)malloc(1024 * 1024 * 1024); |
| 354 CHECK(b); |
| 355 for (int i = 0; i < 1024 * 1024 * 1024 / 4096; ++i) { |
| 356 b[i * 4096] = 'a'; |
| 357 } |
| 358 LOG(ERROR) << "allocated 1GB using malloc"; |
| 359 |
| 360 LOG(ERROR) << "try to allocate 1GB using virtual alloc"; |
| 361 for (int i = 0; i < 1024; i++) { |
| 362 char* a = (char*)VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT | MEM_RESERVE, P
AGE_READWRITE); |
| 363 CHECK(a); |
| 364 a[0] = 'a'; |
| 365 } |
| 366 LOG(ERROR) << "allocated 1GB using virtual alloc"; |
| 367 } |
| 368 |
| 369 void swap2_child() { |
| 370 base::Process process(base::Process::Current()); |
| 371 SavedStats first, second; |
| 372 SaveStats(&first, &process); |
| 373 for (int i = 0; i < 1024; i++) { |
| 374 char* a = (char*)VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT | MEM_RESERVE, P
AGE_READWRITE); |
| 375 |
| 376 // Occasional errors. |
| 377 if (!a) |
| 378 continue; |
| 379 |
| 380 a[0] = 'a'; |
| 381 } |
| 382 SaveStats(&second, &process); |
| 383 DumpDiff(&first, &second); |
| 384 } |
| 385 |
| 386 int main(int argc, char* argv[]) { |
| 387 base::CommandLine::Init(argc, argv); |
| 388 const base::CommandLine& current_command = |
| 389 *base::CommandLine::ForCurrentProcess(); |
| 390 |
| 391 if (current_command.HasSwitch("sleepy")) { |
| 392 new char[base::Process::Current().Pid()]; |
| 393 SleepForever(); |
| 394 } |
| 395 |
| 396 if (current_command.HasSwitch("malloc1")) { |
| 397 malloc1(); |
| 398 SleepForever(); |
| 399 } |
| 400 |
| 401 if (current_command.HasSwitch("malloc2")) { |
| 402 malloc2(); |
| 403 SleepForever(); |
| 404 } |
| 405 |
| 406 if (current_command.HasSwitch("malloc3")) { |
| 407 malloc3(); |
| 408 SleepForever(); |
| 409 } |
| 410 |
| 411 if (current_command.HasSwitch("malloc4")) { |
| 412 malloc4(); |
| 413 SleepForever(); |
| 414 } |
| 415 |
| 416 if (current_command.HasSwitch("anonymous_file_mapping1")) { |
| 417 anonymous_file_mapping1(); |
| 418 SleepForever(); |
| 419 } |
| 420 |
| 421 if (current_command.HasSwitch("anonymous_file_mapping2")) { |
| 422 anonymous_file_mapping2(); |
| 423 SleepForever(); |
| 424 } |
| 425 if (current_command.HasSwitch("anonymous_file_mapping3")) { |
| 426 anonymous_file_mapping3(); |
| 427 SleepForever(); |
| 428 } |
| 429 if (current_command.HasSwitch("virtual_alloc1")) { |
| 430 virtual_alloc1(); |
| 431 SleepForever(); |
| 432 } |
| 433 if (current_command.HasSwitch("virtual_alloc2")) { |
| 434 virtual_alloc2(); |
| 435 SleepForever(); |
| 436 } |
| 437 if (current_command.HasSwitch("virtual_alloc3")) { |
| 438 virtual_alloc3(); |
| 439 SleepForever(); |
| 440 } |
| 441 if (current_command.HasSwitch("virtual_alloc4")) { |
| 442 virtual_alloc4(); |
| 443 SleepForever(); |
| 444 } |
| 445 if (current_command.HasSwitch("virtual_alloc5")) { |
| 446 virtual_alloc4(); |
| 447 SleepForever(); |
| 448 } |
| 449 if (current_command.HasSwitch("heap_alloc1")) { |
| 450 heap_alloc1(); |
| 451 SleepForever(); |
| 452 } |
| 453 if (current_command.HasSwitch("heap_alloc2")) { |
| 454 heap_alloc2(); |
| 455 SleepForever(); |
| 456 } |
| 457 if (current_command.HasSwitch("heap_alloc3")) { |
| 458 heap_alloc3(); |
| 459 SleepForever(); |
| 460 } |
| 461 if (current_command.HasSwitch("leak_1gb")) { |
| 462 leak_1gb(); |
| 463 SleepForever(); |
| 464 } |
| 465 if (current_command.HasSwitch("swap1")) { |
| 466 swap1(); |
| 467 SleepForever(); |
| 468 } |
| 469 if (current_command.HasSwitch("swap2_child")) { |
| 470 swap2_child(); |
| 471 SleepForever(); |
| 472 } |
| 473 if (current_command.HasSwitch("swap2")) { |
| 474 swap2(); |
| 475 SleepForever(); |
| 476 } |
| 477 // Launch 2 child processes to mess with. |
| 478 // base::LaunchOptions options; |
| 479 base::Process a(base::Process::Current()); |
| 480 // base::CommandLine child_command(current_command); |
| 481 // child_command.AppendSwitch("sleepy"); |
| 482 // base::Process b(base::LaunchProcess(child_command, options)); |
| 483 // base::Process c(base::LaunchProcess(child_command, options)); |
| 484 |
| 485 // // Test sharing a DLL. |
| 486 // LoadDll(); |
| 487 |
| 488 // // Test shared memory that's not shared. |
| 489 // CreateSharedMemoryButDontShare(); |
| 490 |
| 491 // // Test sharing with just one other process. |
| 492 // ShareMeSomeMemory(2, {&b}); |
| 493 // ShareMeSomeMemory(4, {&c}); |
| 494 |
| 495 // // Test sharing with bunches of processes. |
| 496 // ShareMeSomeMemory(7, {&b, &c}); |
| 497 |
| 498 // // Test sharing Copy on write pgae. |
| 499 // ShareMeSomeCOWsAndTouchThem(12, {&b, &c}); |
| 500 |
| 501 // // Force us to swap out memory. |
| 502 // ForceSwap(); |
| 503 // DumpStats({&a}); |
| 504 |
| 505 // for (int i = 0; i < 1000; ++i) { |
| 506 // char*a = (char*)malloc(4096 * 16); |
| 507 // *a = 'a'; |
| 508 // // for (int j = 0; j < 16; ++j) |
| 509 // // a[j * 4096] = 'a'; |
| 510 // // base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); |
| 511 // } |
| 512 |
| 513 // // Print out stats. |
| 514 // DumpStats({&a}); |
| 515 //DumpStats({&a, &b, &c}); |
| 516 |
| 517 // Wait until input so it can be inspected. |
| 518 char dummy; |
| 519 scanf("%c", &dummy); |
| 520 |
| 521 // Clean up 2 child processes. |
| 522 // b.Terminate(0, true); |
| 523 // c.Terminate(0, true); |
| 524 |
| 525 return 0; |
| 526 } |
| OLD | NEW |