-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement auto-restart logic for processing large numbers of parts in a batch process. #26
Comments
It appears that the runaway handle count is related to thread handles to zombie PIDs. Solid Edge is opening threads but not closing the thread handles when it's done with them, causing handle count to grow endlessly. The following C++ code will find and force-close thread handles to zombie PIDs given a process name. Try porting it to P/Invoke functions directly from the Windows DLL's so it doesn't require any additional C++ code to work. After that, establish some form of zombie handle monitor as an application extension or util. #include "pch.h"
#include "KillZombieThreadHandles.h"
#include <windows.h>
#include <TlHelp32.h>
#include <cstdio>
#include <cwchar>
#include <string>
#include <iostream>
#include <bcrypt.h>
#include <memory>
#pragma comment(lib, "ntdll")
#define NT_SUCCESS(status) (status >= 0)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
enum PROCESSINFOCLASS {
ProcessHandleInformation = 51
};
typedef struct _PROCESS_HANDLE_TABLE_ENTRY_INFO {
HANDLE HandleValue;
ULONG_PTR HandleCount;
ULONG_PTR PointerCount;
ULONG GrantedAccess;
ULONG ObjectTypeIndex;
ULONG HandleAttributes;
ULONG Reserved;
} PROCESS_HANDLE_TABLE_ENTRY_INFO, * PPROCESS_HANDLE_TABLE_ENTRY_INFO;
// private
typedef struct _PROCESS_HANDLE_SNAPSHOT_INFORMATION {
ULONG_PTR NumberOfHandles;
ULONG_PTR Reserved;
PROCESS_HANDLE_TABLE_ENTRY_INFO Handles[1];
} PROCESS_HANDLE_SNAPSHOT_INFORMATION, * PPROCESS_HANDLE_SNAPSHOT_INFORMATION;
extern "C" NTSTATUS NTAPI NtQueryInformationProcess(
_In_ HANDLE ProcessHandle,
_In_ PROCESSINFOCLASS ProcessInformationClass,
_Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation,
_In_ ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength);
static DWORD GetPIDFromProcessName(wchar_t* processName) {
HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return 0;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
// skip the idle process
::Process32First(hSnapshot, &pe);
DWORD pid = 0;
while (::Process32Next(hSnapshot, &pe)) {
if (::_wcsicmp(pe.szExeFile, processName) == 0) {
// found it!
pid = pe.th32ProcessID;
break;
}
}
::CloseHandle(hSnapshot);
return pid;
}
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
} OBJECT_INFORMATION_CLASS;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef struct _OBJECT_NAME_INFORMATION {
UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;
typedef struct _OBJECT_TYPE_INFORMATION {
UNICODE_STRING TypeName;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;
extern "C" NTSTATUS NTAPI NtQueryObject(
_In_opt_ HANDLE Handle,
_In_ OBJECT_INFORMATION_CLASS ObjectInformationClass,
_Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation,
_In_ ULONG ObjectInformationLength,
_Out_opt_ PULONG ReturnLength);
static void PrintUnicodeString(const UNICODE_STRING* unicodeString) {
int bufferSize = WideCharToMultiByte(CP_UTF8, 0, unicodeString->Buffer, unicodeString->Length / sizeof(WCHAR), nullptr, 0, nullptr, nullptr);
if (bufferSize > 0) {
char* buffer = new char[bufferSize + 1];
WideCharToMultiByte(CP_UTF8, 0, unicodeString->Buffer, unicodeString->Length / sizeof(WCHAR), buffer, bufferSize, nullptr, nullptr);
buffer[bufferSize] = '\0';
printf("%s\n", buffer);
delete[] buffer;
}
}
static bool IsThreadType(UNICODE_STRING* typeName) {
// Ensure the UNICODE_STRING is null-terminated
std::wstring typeNameStr(typeName->Buffer, typeName->Length / sizeof(WCHAR));
typeNameStr.push_back(L'\0');
// Compare with "Thread"
return wcscmp(typeNameStr.c_str(), L"Thread") == 0;
}
KILLZOMBIETHREADHANDLES_API bool KillZombieThreadHandles(wchar_t* processName)
{
DWORD pid = GetPIDFromProcessName(processName);
if (pid == 0) {
printf("Failed to locate Solid Edge\n");
return true;
}
printf("Located Solid Edge: PID=%lu\n", pid);
HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE,
FALSE, pid);
if (!hProcess) {
DWORD error = GetLastError();
std::cerr << "Failed to open Solid Edge process handle. Error: " << error << std::endl;
return true;
}
ULONG size = 1 << 10;
std::unique_ptr<BYTE[]> buffer;
for (;;) {
buffer = std::make_unique<BYTE[]>(size);
auto status = ::NtQueryInformationProcess(hProcess, ProcessHandleInformation,
buffer.get(), size, &size);
if (NT_SUCCESS(status))
break;
if (status == STATUS_INFO_LENGTH_MISMATCH) {
size += 1 << 10;
continue;
}
DWORD error = GetLastError();
std::cerr << "Error enumerating handles: " << error << std::endl;
return true;
}
auto info = reinterpret_cast<PROCESS_HANDLE_SNAPSHOT_INFORMATION*>(buffer.get());
USHORT zombieThreadHandleCount = 0;
DWORD sessionId;
::ProcessIdToSessionId(pid, &sessionId);
for (ULONG i = 0; i < info->NumberOfHandles; i++) {
HANDLE h = info->Handles[i].HandleValue;
HANDLE hTarget;
if (!::DuplicateHandle(hProcess, h, ::GetCurrentProcess(), &hTarget,
0, FALSE, DUPLICATE_SAME_ACCESS))
continue; // move to next handle
BYTE nameBuffer[1 << 10];
BYTE typeBuffer[1 << 10];
auto status = ::NtQueryObject(hTarget, ObjectNameInformation,
nameBuffer, sizeof(nameBuffer), nullptr);
auto status2 = ::NtQueryObject(hTarget, ObjectTypeInformation,
typeBuffer, sizeof(typeBuffer), nullptr);
auto type = reinterpret_cast<UNICODE_STRING*>(typeBuffer);
if (IsThreadType(type)) {
DWORD flags;
if (!GetHandleInformation(hTarget, &flags)) {
DWORD error = GetLastError();
std::cerr << "Invalid thread handle. Error: " << error << std::endl;
}
DWORD threadHandleId = GetProcessIdOfThread(hTarget);
if (threadHandleId == 0) {
DWORD error = GetLastError();
std::cerr << "Failed to get process ID of thread. Error: " << error << std::endl;
}
HANDLE hThread = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, threadHandleId);
if (hThread == nullptr) {
DWORD error = GetLastError();
if (error == ERROR_INVALID_PARAMETER) {
std::cerr << "The process is not running." << std::endl;
}
else {
std::cerr << "Failed to open process. Error: " << error << std::endl;
}
return false;
}
DWORD exitCode;
if (!GetExitCodeProcess(hThread, &exitCode)) {
DWORD error = GetLastError();
std::cerr << "Failed to get exit code of process. Error: " << error << std::endl;
CloseHandle(hThread);
return false;
}
if (exitCode != STILL_ACTIVE) {
zombieThreadHandleCount++;
if (!::DuplicateHandle(hProcess, h, nullptr, nullptr,
0, FALSE, DUPLICATE_CLOSE_SOURCE)) {
DWORD error = GetLastError();
std::cerr << "Failed to close handle. Error: " << error << std::endl;
}
CloseHandle(hThread);
}
}
::CloseHandle(hTarget);
if (!NT_SUCCESS(status))
{
DWORD error = GetLastError();
std::cerr << "Unknown error retrieving handle. Error: " << error << std::endl;
printf("%ld\r\n", status);
continue;
}
}
printf("Killed %d zombie handles\r\n", zombieThreadHandleCount);
return true;
}
''' |
Even when closing files correctly while running SE fully backgrounded, Solid Edge has an issue leaking handles, causing balooning of RAM usage and progressive slowdowns over time.
In the past I've addressed this issue by monitoring the number of files processed and restarting the SE app at X intervals. I've also implemented a handle monitor which will read the handle count from the Edge process in a background thread and provide thread-safe non-blocking access to the handle count.
This would make a good addition to the AppHelper class. Implement HandleMonitor (and maybe a simple count monitor to manually update when processing a part? Monitor for closed files and bump counter?) so user can check for conditions and restart the Solid Edge app based on criteria without boilerplate.
The text was updated successfully, but these errors were encountered: