mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-06-01 17:15:48 +08:00
fix(macos,process_info): tighten error handling and add endian assert
- Guard against `getProcess` returning null in the macOS FFI path (`proc_pidpath` can fail with EPERM or for an already-exited PID). Stop unwrapping with `!` and only populate the ProcessInfoCache on success; the previous code threw NullThrownError there, which the outer try/catch silently swallowed and prevented caching of valid later results. - Add a 5-second negative cache for ports whose owner can't be resolved (typical for short-lived clients that exited before we scanned). Without it every such request triggered a full PID list rescan, defeating the point of the pid cache for the failure path. - Add a debug-only assert that the host is little-endian. The libproc parsing reads `(int)htons(port)` by treating the low two bytes as a big-endian uint16, which requires a little-endian host. All shipping macOS hardware satisfies this; the assert fails loudly if the assumption is ever broken (stripped from release builds). Re-verified on macOS 26.4: 500 concurrent curl through the proxy, single ProxyPin process throughout, 9099 freed immediately on exit.
This commit is contained in:
@@ -45,6 +45,12 @@ class ProcessInfoUtils {
|
||||
// libproc scan runs on the main isolate.
|
||||
static final _pidCache = ExpiringCache<String, int>(const Duration(seconds: 30));
|
||||
|
||||
// Negative cache for ports whose owner can't be resolved (e.g. the client
|
||||
// process has already exited by the time we scan). Without this, every
|
||||
// short-lived connection forces a full PID-list rescan on every request.
|
||||
// Short TTL so a real owner that appears soon after is not masked.
|
||||
static final _pidNotFoundCache = ExpiringCache<String, bool>(const Duration(seconds: 5));
|
||||
|
||||
static Future<ProcessInfo?> getProcessByPort(InetSocketAddress socketAddress, String cacheKeyPre) async {
|
||||
try {
|
||||
if (Platform.isAndroid) {
|
||||
@@ -59,10 +65,14 @@ class ProcessInfoUtils {
|
||||
}
|
||||
|
||||
var addrKey = "${socketAddress.host}:${socketAddress.port}";
|
||||
if (_pidNotFoundCache.get(addrKey) == true) return null;
|
||||
var pid = _pidCache.get(addrKey);
|
||||
if (pid == null) {
|
||||
pid = await _getPid(socketAddress);
|
||||
if (pid == null) return null;
|
||||
if (pid == null) {
|
||||
_pidNotFoundCache.set(addrKey, true);
|
||||
return null;
|
||||
}
|
||||
_pidCache.set(addrKey, pid);
|
||||
}
|
||||
|
||||
@@ -71,7 +81,9 @@ class ProcessInfoUtils {
|
||||
if (processInfo != null) return processInfo;
|
||||
|
||||
processInfo = await getProcess(pid);
|
||||
processInfoCache.set(cacheKey, processInfo!);
|
||||
if (processInfo != null) {
|
||||
processInfoCache.set(cacheKey, processInfo);
|
||||
}
|
||||
return processInfo;
|
||||
} catch (e) {
|
||||
logger.e("getProcessByPort error: $e");
|
||||
|
||||
@@ -93,6 +93,13 @@ class MacosProcessInfo {
|
||||
/// are spawned. Typical cost on a desktop with ~500 processes and ~10k
|
||||
/// total fds is a few milliseconds.
|
||||
static int? findPidByLocalTcpPort(int localPort) {
|
||||
// The insi_lport read below assumes a little-endian host: `(int)htons(port)`
|
||||
// stores the network-byte-order 16-bit port in the low two bytes of the
|
||||
// int32 field. All shipping macOS hardware (x86_64 / arm64) is
|
||||
// little-endian; this assert exists to fail loudly rather than return
|
||||
// wrong port values if that ever changes. Stripped in release builds.
|
||||
assert(Endian.host == Endian.little, 'libproc parsing requires little-endian host');
|
||||
|
||||
final pidBufSize = _procListPids(_kProcAllPids, 0, nullptr, 0);
|
||||
if (pidBufSize <= 0) return null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user