Stuck on the "Path appears valid" error

Hello,

I am trying to get Hops working with Rhino 8. I have been reading similar topics here on the forums and have tried a fix related to the *.runtimeconfig.json files of rhino.compute and compute.geometry but unfortunately this didn’t work.

The fix I applied was based on this topic: Grasshopper Hops doesn’t work on RH8 SR17 - Rhino Developer / compute.rhino3d - McNeel Forum

Based on this I altered both

%AppData%\McNeel\Rhinoceros\packages\8.0\Hops\0.16.18\compute.geometry\computeg.geometry.runtimeconfig.json

{
    "runtimeOptions": {
        "tfm": "net8.0",
        "rollForward": "Major",
        "frameworks": [
            {
                "name": "Microsoft.NETCore.App",
                "version": "8.0.25"
            },
            {
                "name": "Microsoft.WindowsDesktop.App",
                "version": "8.0.25"
            },
            {
                "name": "Microsoft.AspNetCore.App",
                "version": "8.0.25"
            }
        ],
        "configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true,
"CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false
        }
    }
}

%AppData%\McNeel\Rhinoceros\packages\8.0\Hops\0.16.28\rhino.compute\rhino.compute.runtimeconfig.json

{
    "runtimeOptions": {
        "tfm": "net8.0",
        "includedFrameworks": [
            {
                "name": "Microsoft.NETCore.App",
                "version": "8.0.25"
            },
            {

                "name": "Microsoft.AspNetCore.App",
                "version": "8.0.25"
            }
        ],
        "configProperties": {
            "System.GC.Server": true,
            "System.Reflection.Metadata.MetadataUpdater.IsSupported": false,            "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
        }
    }
}

My installed dotnet runtimes are:

Given this setup, I keep getting the error: Path appears valid, but to something that is not Hops related.

When I start Rhino and open my GH definition, Hops seems to work fine for a while. However, I see that when the rhino.compute instance (the one bundled with Rhino locally) goes to sleep afeter some period of inactivity or when locking my laptop, I consistently get this error.

Based on this topic Error: Path appears valid, but to something that is not Hops related - Grasshopper / Hops - McNeel Forum I see that the rollForward setting might do something

I do believe modifying the *.runtimeconfig.json files can be the fix since Hops is at least working to some extent (it didn’t at all before). However, I am still missing something.

My latest insight is that the runtimeconfig of compute.geometry looks a bit different than the rhino.compute one. I specifically notice the frameworksattribute for compute.geometry vs. the includedFrameworks attribute for rhino.compute. Could this be something I need to look into?

I have tried to investigate some more why Hops stops working after the child proces goes idle. I had a look at the Rhino compute code on Github and had AI help me write a report. Maybe it helps us look in the right direction for a fix.

It mentioned the following:

After an idle period, compute.geometry (child process on port 6001) shuts itself down as expected via the idle span mechanism. However, when a new Hops request comes in afterwards, rhino.compute (parent on port 6500) fails with a 500 error / HttpRequestException: Connection refused (localhost:6001) instead of spawning a new child process.

Expected behavior: rhino.compute should detect the dead child, remove it from the process pool, spawn a replacement, and fulfill the request.

Actual behavior: The request fails immediately with connection refused. Subsequent requests also fail. Recovery requires manually restarting the service.

Root cause (from source code analysis of compute.rhino3d):

There are two issues working together:

  1. Race condition in ComputeChildren.GetComputeServerBaseUrl() (ComputeChildren.cs):
    The round-robin scheduler checks Process.HasExited to determine if a child is alive. However, when compute.geometry calls app.StopAsync() during idle shutdown, the TCP port is closed first (socket stops listening), but the OS process may still be running briefly during its shutdown sequence. This creates a window where HasExited returns false (process still alive) but port 6001 is already closed — so the request gets forwarded to a dead socket.

  2. Missing error handling in ReverseProxyHandler() (ReverseProxy.cs):
    The proxy handler only catches ConnectionResetException and OperationCanceledException. It does not catch HttpRequestException (which is what you get on connection refused). So instead of removing the dead child from the queue and retrying with a fresh spawn, the exception propagates as a 500 error to the caller.

How the idle shutdown triggers it:

In Shutdown.cs, the child process periodically asks the parent GET /idlespan — “how long since your last proxied request?” If the parent’s idle span (plus a 10% buffer) exceeds the configured limit, the child shuts down. This is by design. The problem is that nothing in the parent notices or reacts to the child being gone until a request actually comes in, and at that point the race condition above prevents proper recovery.

Suggested fixes (any one of these would resolve it):

  1. Catch HttpRequestException in ReverseProxyHandler — on connection refused, evict the child from the queue and retry via GetComputeServerBaseUrl() (which would then see HasExited == true and trigger LaunchCompute()).

  2. Add a port-liveness check in GetComputeServerBaseUrl() — check IsPortOpen(port) in addition to Process.HasExited before considering a child alive. The IsPortOpen helper already exists in the codebase.

  3. Add a retry loop in the proxy handler with a configurable retry count (e.g., 1-2 retries) that on connection failure forces a re-evaluation of the child pool.

Reproduction steps:

  1. Start rhino.compute with --spawn-on-startup and an idle span configured (e.g., --idlespan 3600)

  2. Let the system sit idle longer than the configured idle span

  3. Observe compute.geometry logging “Idle span limit reached” and shutting down

  4. Send a Hops request from Grasshopper

  5. Result: 500 error with “connection refused” on localhost:6001

After investigating some more. I believe the %AppData%\McNeel\Rhinoceros\packages\8.0\Hops\0.16.28\rhino.compute\rhino.compute.runtimeconfig.json should just be:

{
"runtimeOptions": {
"tfm": "net8.0",
"includedFrameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
{

"name": "Microsoft.AspNetCore.App",
"version": "8.0.0"
}
],
"configProperties": {
"System.GC.Server": true,
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false, "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

This file shouldn’t have been edited since this concerns a bundled .NET runtime with the rhino.compute application.

For %AppData%\McNeel\Rhinoceros\packages\8.0\Hops\0.16.18\compute.geometry\computeg.geometry.runtimeconfig.jsoni have tried setting the rollForward to LatestMinor to make sure it always uses the latest .NET 8 version available.

Unfortunately, these fixes still didn’t resolve the issue. After an hour when the idlespan time limit is reached, hops starts giving the error: “Path appears valid, but to something that is not Hops related” again. In the Rhino.compute console I see:

CG [09:59:44 DBG] The elapsed time has exceeded the idle span limit
CG [09:59:44 DBG] Checking with parent process at ``http://localhost:6500/idlespan``.
RC [09:59:44 DBG] Request received to /idlespan endpoint
RC [09:59:44 INF] HTTP GET /idlespan responded 200 in 3.7735 ms
CG [09:59:44 DBG] Response received. Parent process idle span is 3594 seconds.
CG [09:59:44 DBG] Idle span limit reached
CG [09:59:44 DBG] Shutting down child process
CG [09:59:44 INF] Total elapsed time for child process is 00 days, 01 hrs, 00 mins, 01 secs
CG [09:59:44 DBG] Hosting stopping
CG [09:59:44 INF] Application is shutting down…
CG [09:59:44 DBG] Hosting stopping
CG [09:59:44 DBG] Hosting stopped
CG [09:59:44 DBG] Hosting stopped
RC [10:00:38 INF] Max concurrent requests = 0
RC [10:00:50 ERR] HTTP POST /grasshopper responded 500 in 4096.8813 ms
System.Net.Http.HttpRequestException: Kan geen verbinding maken omdat de doelcomputer de verbinding actief heeft geweigerd. (localhost:6001)
—> System.Net.Sockets.SocketException (10061): Kan geen verbinding maken omdat de doelcomputer de verbinding actief heeft geweigerd.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
at System.Net.Sockets.Socket.g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
— End of inner exception stack trace —
at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(QueueItem queueItem)
at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
at rhino.compute.ReverseProxyModule.SendProxyRequest(HttpRequest initialRequest, HttpMethod method, String baseurl) in D:\BuildAgent\work\4852f584398a78e4\src\rhino.compute\ReverseProxy.cs:line 145
at rhino.compute.ReverseProxyModule.ReverseProxyGrasshopper(HttpRequest req, HttpResponse res) in D:\BuildAgent\work\4852f584398a78e4\src\rhino.compute\ReverseProxy.cs:line 201
at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)

This for me also points to a bug with restarting the the compute.geometry service after the idlespan time limit is reached.

I did a fresh reinstall of Hops (v0.16.28) just to be sure but the error still persists. After the server start idling, it cannot start up again and hops breaks.

Hi Jeroen. I’m sorry to hear you’re having trouble. So it sounds like you’ve updated to the latest version of Hops and it seems to work before it shuts itself down because of the idle span, is that correct?
In theory here’s what should happen. Let’s say you’re rhino.compute instance shuts down your child processes after the idlespan limit has been reached. If you then send it a new request (let’s say you have a Hops component and you make a change to one of the input values) then the rhino.compute process will automatically handle spinning a child process back up and compute the request for you. You shouldn’t have to do anything special to “wake” it back up. It’s handled automatically. I just tested this on my own machine and confirm it does work this way, so if you’re seeing something else, then perhaps something is broken.
It does seem like you’ve messed around with a lot of the settings in rhino.compute. That shouldn’t be necessary. You shouldn’t have to change any of the json config files or anything. The reason that existed was because originally we didn’t ship the correct ASP .NET runtimes with Rhino. But, about 6-12 months ago, we modified the Rhino installer to automatically install the correct runtimes… so assuming you’re using an up to date version of Rhino and an up to date version of Hops, then everything should just work out of the box. You shouldn’t have to change anything to get things working. Perhaps you changed things in your configuration which are now causing problems, I don’t know. But, the children should automatically get restarted if a new request is sent to it.

Hi Andy,

Thanks for your reply!

Your correct that Hops works fine before the idlespan limit is reached. Also, I do have the correct behaviour you describe when running Rhino.compute as a separate process and filling in a localhost adres in the Hops settings. However, when letting Rhino/Grasshoppper start Rhino compute during startup I get the behaviour that after idling, the server is unable to restart.

For added context, I did reinstall Hops from scratch again and I made sure only the latest version is available on my system so I consider that I clean install in that regard.

In our case, since we are just getting started with Hops (and I have some collegues which are less technical), it would be easiest if the local rhino compute server that starts up with Rhino just works without workarounds. I know the problem is technically solved when using a remote rhino compute server but it takes time within an enterprise context to set that up so that is unfortunately not a solution at this point in time.

On a side note, perhaps the setting to set the idlespan length can be added to the Hops settings? This will also make it easier to test restart behaviour for rhino compute when handled by Rhino itself. Now, it is rather cumbersome to wait an hour before knowing whether the problem still persists.

In summary: Rhino.Compute restarts properly in a remote context but not when started by Rhino itself during startup (from the solver settings).

Hi Jeroen,
I’ve just pushed a very large change to Hops/Rhino.Compute (version 0.17.0). Can you try downloading this version as well as making sure you’re using the latest Rhino 8 service release and see if this fixes your issue. Lastly, please make sure that you are using the RHINO_COMPUTE_DEBUG environment variable to ensure you’re getting the most verbose logging information. You can read more about how to enable this here.