Skip to content

Commit

Permalink
feat(windows): suspend entire process tree
Browse files Browse the repository at this point in the history
Suspend / resume child processes recursively on
Windows, the same as we do on Linux. Improves
reliability and performance gains for processes
that spawn many child processes.
  • Loading branch information
Merrit committed Oct 3, 2024
1 parent 5336b68 commit 1547733
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 0 deletions.
53 changes: 53 additions & 0 deletions lib/native_platform/src/process/repository/src/win32/win32.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// ignore_for_file: non_constant_identifier_names, constant_identifier_names

import 'dart:convert';
import 'dart:ffi';

final _kernel32 = DynamicLibrary.open('kernel32.dll');

final CreateToolhelp32Snapshot =
_kernel32.lookupFunction<IntPtr Function(Uint32, Uint32), int Function(int, int)>(
'CreateToolhelp32Snapshot');

final Process32First = _kernel32.lookupFunction<
Int32 Function(IntPtr hSnapshot, Pointer<PROCESSENTRY32> lppe),
int Function(int hSnapshot, Pointer<PROCESSENTRY32> lppe)>('Process32First');

final Process32Next = _kernel32.lookupFunction<
Int32 Function(IntPtr hSnapshot, Pointer<PROCESSENTRY32> lppe),
int Function(int hSnapshot, Pointer<PROCESSENTRY32> lppe)>('Process32Next');

final class PROCESSENTRY32 extends Struct {
@Int32()
external int dwSize;
@Int32()
external int cntUsage;
@Int32()
external int th32ProcessID;
external Pointer<Uint32> th32DefaultHeapID;
@Int32()
external int th32ModuleID;
@Int32()
external int cntThreads;
@Int32()
external int th32ParentProcessID;
@Int32()
external int pcPriClassBase;
@Int32()
external int dwFlags;
@Array(260)
external Array<Uint8> _szExeFile;
String get szExeFile => _unwrap(_szExeFile);
}

String _unwrap(Array<Uint8> bytes) {
String buf = "";
int i = 0;
while (bytes[i] != 0) {
buf += utf8.decode([bytes[i]]);
i += 1;
}
return buf;
}

const TH32CS_SNAPPROCESS = 0x00000002;
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:win32_suspend_process/win32_suspend_process.dart';

import '../../../../../logs/logs.dart';
import '../../process.dart';
import 'win32/win32.dart';

/// Provides interaction access with host system processes on Windows.
class Win32ProcessRepository extends ProcessRepository {
Expand Down Expand Up @@ -101,6 +102,15 @@ class Win32ProcessRepository extends ProcessRepository {
final successful = (result == 0);
log.i('Resuming $pid was successful: $successful');
CloseHandle(processHandle);

if (successful) {
// Resume child processes recursively.
final childPids = await _getChildProcesses(pid);
for (final childPid in childPids) {
await resume(childPid);
}
}

return successful;
}

Expand All @@ -116,9 +126,46 @@ class Win32ProcessRepository extends ProcessRepository {
final successful = (result == 0);
log.i('Suspending $pid was successful: $successful');
CloseHandle(processHandle);

if (successful) {
// Suspend child processes recursively.
final childPids = await _getChildProcesses(pid);
for (final childPid in childPids) {
await suspend(childPid);
}
}

return successful;
}

/// Returns a list of child processes for the provided [pid].
Future<List<int>> _getChildProcesses(int pid) async {
final childPids = <int>[];
final snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) {
log.w('Failed to create snapshot: ${GetLastError()}');
return childPids;
}

final processEntry = calloc<PROCESSENTRY32>();
processEntry.ref.dwSize = sizeOf<PROCESSENTRY32>();

final isProcessFound = Process32First(snapshot, processEntry) == TRUE;
if (isProcessFound) {
do {
if (processEntry.ref.th32ParentProcessID == pid) {
childPids.add(processEntry.ref.th32ProcessID);
}
} while (Process32Next(snapshot, processEntry) == TRUE);
} else {
log.w('Failed to retrieve first process: ${GetLastError()}');
}

CloseHandle(snapshot);
calloc.free(processEntry);
return childPids;
}

/// Returns true if the process is suspended, false otherwise.
bool _isProcessSuspended(int pid) {
return _isProcessSuspendedNative(pid) == 1;
Expand Down

0 comments on commit 1547733

Please sign in to comment.