@@ -176,9 +176,9 @@ internal static class Program
176176 // replaced immediately instead of waiting out the normal gap.
177177 private static volatile bool circuitWatchWarmup ;
178178
179- // Ping-based tunnel warmup: one generate_204 request per second for
180- // warmupSeconds, replacing the old 1 MiB download check (which gave
181- // false negatives on cold single-stream circuits).
179+ // Ping-based tunnel warmup: one generate_204 request dispatched every
180+ // second for warmupSeconds, replacing the old 1 MiB download check
181+ // (which gave false negatives on cold single-stream circuits).
182182 private static int warmupSeconds = 60 ;
183183
184184 [ DllImport ( "wininet.dll" , SetLastError = true ) ]
@@ -2245,27 +2245,50 @@ private static int PingSocks(string host, string path, out long elapsedMs)
22452245 }
22462246 }
22472247
2248- // Warms the tunnel with generate_204 pings through the HTTP proxy
2249- // (127.0.0.1:8118) instead of a 1 MiB download, which produced false
2250- // negatives on cold single-stream circuits. One request per second for
2251- // warmupSeconds, each with a 5 s timeout. Every request is timed and
2252- // counted; the tunnel is ready when at least 5 pings succeeded. C
2253- // aborts (stops tor, back to the main menu).
2254- // The ping loop runs on a worker thread while the caller polls for C, so
2248+ // Warms the tunnel with real SOCKS5 generate_204 pings (127.0.0.1:9050)
2249+ // instead of a 1 MiB download, which produced false negatives on cold
2250+ // single-stream circuits. One request is dispatched every second for
2251+ // warmupSeconds; each request is allowed up to 5 s to get a response and
2252+ // is recorded as ok / fail / timeout. Requests overlap: a slow one never
2253+ // stalls the 1/s cadence (the old loop tried up to 3 hosts sequentially
2254+ // with 5 s timeouts each, stretching a 60 s warmup to many minutes on a
2255+ // slow tunnel). The whole phase is therefore bounded to about
2256+ // warmupSeconds + 5 s (the last request's timeout), i.e. ~65 s. The
2257+ // tunnel is ready when at least 5 pings succeeded. C aborts (stops tor,
2258+ // back to the main menu).
2259+ // The ping loop runs on worker threads while the caller polls for C, so
22552260 // abort works even when a request is stuck on a slow circuit. After the
22562261 // loop, conflux linkage is reported honestly: pings succeed on plain
22572262 // circuits even when no conflux leg has linked yet.
2263+ private static readonly object warmupLock = new object ( ) ;
22582264 private static volatile bool warmupAbort ;
22592265 private static volatile bool warmupRunning ;
22602266 private static volatile int warmupDone , warmupOk , warmupFail , warmupTimeouts ;
22612267 private static long warmupTotalMs , warmupBestMs , warmupWorstMs ;
2268+ private static Stopwatch warmupPhaseSw ;
2269+
2270+ private static string FormatClock ( long ms )
2271+ {
2272+ TimeSpan ts = TimeSpan . FromMilliseconds ( ms ) ;
2273+ return ( ( int ) ts . TotalMinutes ) . ToString ( "D2" ) + ":" + ts . Seconds . ToString ( "D2" ) ;
2274+ }
22622275
22632276 private static void DrawWarmupLine ( )
22642277 {
2265- string line = "warmup " + warmupDone + "/" + warmupSeconds + " ok=" +
2266- warmupOk + " fail=" + warmupFail + " avg=" +
2267- ( warmupOk > 0 ? warmupTotalMs / warmupOk : 0 ) +
2268- "ms worst=" + warmupWorstMs + "ms" ;
2278+ int done = 0 , ok = 0 , fail = 0 , timeouts = 0 ;
2279+ long total = 0 , worst = 0 , phase = 0 ;
2280+ lock ( warmupLock )
2281+ {
2282+ done = warmupDone ; ok = warmupOk ; fail = warmupFail ; timeouts = warmupTimeouts ;
2283+ total = warmupTotalMs ; worst = warmupWorstMs ;
2284+ if ( warmupPhaseSw != null ) phase = warmupPhaseSw . ElapsedMilliseconds ;
2285+ }
2286+ long cap = ( long ) ( warmupSeconds + 5 ) * 1000 ;
2287+ string line = "warmup " + done + "/" + warmupSeconds + " [" +
2288+ FormatClock ( phase ) + "/" + FormatClock ( cap ) +
2289+ "] ok=" + ok + " fail=" + fail + " timeout=" + timeouts +
2290+ " avg=" + ( ok > 0 ? total / ok : 0 ) +
2291+ "ms worst=" + worst + "ms" ;
22692292 lock ( consoleLock )
22702293 {
22712294 if ( line . Length < progressLineLen ) line = line . PadRight ( progressLineLen ) ;
@@ -2274,10 +2297,31 @@ private static void DrawWarmupLine()
22742297 }
22752298 }
22762299
2300+ private static void WarmupPingWorker ( PingTarget target )
2301+ {
2302+ long ems ;
2303+ int r = PingSocks ( target . Host , target . Path , out ems ) ;
2304+ lock ( warmupLock )
2305+ {
2306+ warmupDone ++ ;
2307+ if ( r == 1 )
2308+ {
2309+ if ( ems < 0 ) ems = 0 ;
2310+ warmupOk ++ ;
2311+ warmupTotalMs += ems ;
2312+ if ( ems < warmupBestMs ) warmupBestMs = ems ;
2313+ if ( ems > warmupWorstMs ) warmupWorstMs = ems ;
2314+ }
2315+ else if ( r == - 1 ) warmupTimeouts ++ ;
2316+ else warmupFail ++ ;
2317+ }
2318+ }
2319+
22772320 private static bool WarmupTunnel ( )
22782321 {
2279- Console . WriteLine ( "warming tunnel: real delay test via SOCKS 127.0.0.1:9050 " +
2280- "every second for " + warmupSeconds + " s (C = stop Tor)..." ) ;
2322+ Console . WriteLine ( "warming tunnel: real delay test via SOCKS 127.0.0.1:9050, " +
2323+ "1 request/s for " + warmupSeconds + " s, 5 s timeout " +
2324+ "(C = stop Tor)..." ) ;
22812325 warmupAbort = false ;
22822326 warmupRunning = true ;
22832327 warmupDone = 0 ;
@@ -2287,47 +2331,26 @@ private static bool WarmupTunnel()
22872331 warmupTotalMs = 0 ;
22882332 warmupBestMs = long . MaxValue ;
22892333 warmupWorstMs = 0 ;
2334+ warmupPhaseSw = Stopwatch . StartNew ( ) ;
22902335
22912336 Thread worker = new Thread ( delegate ( )
22922337 {
2338+ var inflight = new List < Thread > ( ) ;
22932339 for ( int i = 1 ; i <= warmupSeconds ; i ++ )
22942340 {
22952341 if ( warmupAbort ) break ;
2296- bool success = false ;
2297- bool anyTimeout = false ;
2298- long okMs = - 1 ;
2299- Stopwatch sw = Stopwatch . StartNew ( ) ;
2300- foreach ( PingTarget t in PingTargets )
2301- {
2302- long ems ;
2303- int r = PingSocks ( t . Host , t . Path , out ems ) ;
2304- if ( r == 1 )
2305- {
2306- success = true ;
2307- okMs = ems >= 0 ? ems : sw . ElapsedMilliseconds ;
2308- break ;
2309- }
2310- if ( r == - 1 ) anyTimeout = true ;
2311- }
2312- sw . Stop ( ) ;
2313- long elapsedMs = okMs >= 0 ? okMs : sw . ElapsedMilliseconds ;
2314- if ( success )
2315- {
2316- warmupOk ++ ;
2317- warmupTotalMs += elapsedMs ;
2318- if ( elapsedMs < warmupBestMs ) warmupBestMs = elapsedMs ;
2319- if ( elapsedMs > warmupWorstMs ) warmupWorstMs = elapsedMs ;
2320- }
2321- else if ( anyTimeout ) warmupTimeouts ++ ;
2322- else warmupFail ++ ;
2323- warmupDone = i ;
2324- long remaining = 1000 - elapsedMs ;
2325- if ( remaining > 0 && ! warmupAbort )
2326- {
2327- long step = Math . Min ( remaining , 200 ) ;
2328- for ( long t = 0 ; t < remaining && ! warmupAbort ; t += step )
2329- Thread . Sleep ( ( int ) step ) ;
2330- }
2342+ PingTarget target = PingTargets [ ( i - 1 ) % PingTargets . Length ] ;
2343+ Thread t = new Thread ( delegate ( ) { WarmupPingWorker ( target ) ; } ) ;
2344+ t . IsBackground = true ;
2345+ t . Start ( ) ;
2346+ inflight . Add ( t ) ;
2347+ long targetMs = ( long ) i * 1000 ;
2348+ while ( ! warmupAbort && warmupPhaseSw . ElapsedMilliseconds < targetMs )
2349+ Thread . Sleep ( 50 ) ;
2350+ }
2351+ if ( ! warmupAbort )
2352+ {
2353+ foreach ( Thread t in inflight ) t . Join ( ) ;
23312354 }
23322355 warmupRunning = false ;
23332356 } ) ;
@@ -2373,8 +2396,9 @@ private static bool WarmupTunnel()
23732396 }
23742397 int ok = warmupOk , fail = warmupFail , timeouts = warmupTimeouts ;
23752398 long totalMs = warmupTotalMs , bestMs = warmupBestMs , worstMs = warmupWorstMs ;
2399+ long phaseMs = warmupPhaseSw == null ? 0 : warmupPhaseSw . ElapsedMilliseconds ;
23762400 string summary = "warmup done: " + ok + " ok / " + fail + " fail / " + timeouts +
2377- " timeouts in " + warmupSeconds + " s - avg " +
2401+ " timeouts in " + FormatClock ( phaseMs ) + " - avg " +
23782402 ( ok > 0 ? totalMs / ok : 0 ) + " ms, best " +
23792403 ( bestMs == long . MaxValue ? "-" : bestMs . ToString ( ) ) +
23802404 " ms, worst " + worstMs + " ms." ;
0 commit comments