netchx/netch · error · MessageException

{Name} 控制器启动超时

Error message

{Name} 控制器启动超时

What it means

Thrown by Guard.StartGuardAsync when the startup-watch loop (1000 iterations x Task.Delay(50) = ~50 seconds) elapsed without the child printing either a StartedKeyword or a FailedKeyword, and the process did not exit. State stayed Starting the whole time. This is a timeout, distinct from error 2 which is an explicit failure.

Source

Thrown at Netch/Controllers/Guard.cs:101

            // wait ReadOutput change State
            for (var i = 0; i < 1000; i++)
            {
                await Task.Delay(50);
                switch (State)
                {
                    case State.Started:
                        OnStarted();
                        return;
                    case State.Stopped:
                        await StopGuardAsync();
                        OnStartFailed();
                        throw new MessageException($"{Name} 控制器启动失败");
                }
            }

            await StopGuardAsync();
            throw new MessageException($"{Name} 控制器启动超时");
        }
    }

    private async Task ReadOutputAsync(TextReader reader)
    {
        string? line;
        while ((line = await reader.ReadLineAsync()) != null)
        {
            await _logStreamWriter!.WriteLineAsync(line);
            OnReadNewLine(line);

            if (State == State.Starting)
            {
                if (StartedKeywords.Any(s => line.Contains(s)))
                    State = State.Started;
                else if (FailedKeywords.Any(s => line.Contains(s)))
                {
                    OnStartFailed();

View on GitHub (pinned to 9d99eb1c5a)

Solutions

  1. Inspect logging\<Name>.log to see how far the child got and what it last printed.
  2. Update the controller's StartedKeywords to match the installed binary's actual ready line.
  3. Rule out environmental slowdown (antivirus, disk) by launching the binary manually with the same args.
  4. If startup is genuinely slow, the 1000x50ms budget in StartGuardAsync is the knob (requires code change).

Example fix

// before
protected override IEnumerable<string> StartedKeywords { get; } = new[] { "started" };
// after (match the real banner the newer binary emits)
protected override IEnumerable<string> StartedKeywords { get; } = new[] { "started", "running" };
Defensive patterns

Strategy: try-catch

Try / catch

try { await guard.StartGuardAsync(args); }
catch (MessageException ex) when (ex.Message.EndsWith("控制器启动超时"))
{
    Log.Warning("{Name} startup timed out; last log line: {Line}", guard.Name,
        File.ReadLines(guard.LogPath).LastOrDefault());
    // offer a longer retry or a manual launch with the same args
}

Prevention

When it happens

Trigger: Child process starts but hangs before emitting its ready keyword (waiting on network, TUN adapter, slow handshake); StartedKeywords on the Guard subclass don't match what this binary version prints; RedirectOutput=false path is not taken so the loop waits on output that never comes; child blocked on interactive input.

Common situations: Bundled binary upgraded to a version with a different startup banner string, so StartedKeywords no longer matches; slow disk or heavy antivirus scanning delaying child startup past 50s; child waiting on a missing adapter/driver to appear.

Related errors


AI-assisted analysis of netchx/netch@9d99eb1c5a (2026-08-13). Data as JSON: /api/errors/38c0aa73a0756575. Report an issue: GitHub.