Nix build vs nix run

According to the documentation, nix run should be similar to running nix build and then execute the resulting package result in $out/bin/the-installable-app. However, that doesn’t seem to be 100% accurate to me because when I build the flake default app and then run it explicitly the app responds much faster to actions like CTRL+C than when I run it by nix run. In the first case, CTRL+C stops the app immediately with in the second case it takes much longer and I have to repeat the keystroke combination a few times before it really stops.

What is the real difference then?

Thank you in advance.

Do you have an example program where you’re observing this behavior? I tried it out with a few applications now and can’t tell a difference.

This is the real flake: https://github.com/zkSNACKs/WalletWasabi/blob/d7232f92ac950d3142114a66264c53cb69a758bd/flake.nix

Ah I’ll have to test this tomorrow as I’m on MacOS right now. I’ve bookmarked the thread :slight_smile:

1 Like

I’m sorry, but I can’t run this at all. I’m on a x86_64-linux machine now, and I get this error:

$ nix run github:zkSNACKs/WalletWasabi
2023-10-17 19:32:10.752 [1] INFO	InitConfigStartupTask.ExecuteAsync (20)	Wasabi Backend started (e0ce67ee-8ddd-41d2-b518-894cba662bd0).
2023-10-17 19:32:10.827 [9] INFO	Global.DisposeAsync (225)	SegwitTaprootIndexBuilderService is stopped.
2023-10-17 19:32:10.828 [9] INFO	Global.DisposeAsync (228)	TaprootIndexBuilderService is stopped.
2023-10-17 19:32:10.829 [11] INFO	HostedServices.StopAllAsync (88)	Stopped Block Notifier.
2023-10-17 19:32:10.832 [9] INFO	HostedServices.Dispose (147)	Disposed Block Notifier.
2023-10-17 19:32:10.834 [9] INFO	Global.DisposeAsync (235)	P2pNode is disposed.
2023-10-17 19:32:10.847 [7] CRITICAL	Program.Main (19)	System.Net.Http.HttpRequestException: Connection refused (127.0.0.1:8332)
 ---> System.Net.Sockets.SocketException (111): Connection refused
   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.<ConnectAsync>g__WaitForConnectWithCancellation|281_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.HttpConnectionWaiter`1.WaitForConnectionAsync(Boolean async, CancellationToken requestCancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at NBitcoin.RPC.RPCClient.SendCommandAsync(RPCRequest request, CancellationToken cancellationToken)
   at NBitcoin.RPC.RPCClient.GetBlockchainInfoAsync(CancellationToken cancellationToken)
   at WalletWasabi.BitcoinCore.Rpc.RpcClientBase.GetBlockchainInfoAsync(CancellationToken cancellationToken)
   at WalletWasabi.Backend.Global.AssertRpcNodeFullyInitializedAsync(CancellationToken cancellationToken)
   at WalletWasabi.Backend.Global.InitializeAsync(CancellationToken cancel)
   at WalletWasabi.Backend.InitConfigStartupTask.ExecuteAsync(CancellationToken cancellationToken)
   at WalletWasabi.Backend.Extensions.RunWithTasksAsync(IHost host, CancellationToken cancellationToken)
   at WalletWasabi.Backend.Program.Main(String[] args)

What is your setup and what are the exact commands you’re running?

I appreciate you help.

The basic setup requires nix-shell -p bitcoin --run "bitcoind -regtest" (a bitcoin node running in a fake network known as regtest). That should be all i think.

Doesn’t seem to make a difference. I ran the command you showed in one terminal and then tried to start WalletWasabi in another, and it still failed with the same error message.

Are you running that application from a specific revision?

No, it is not the revision, i think some configuration is needed but anyway, i don’t want to waste your time so, better questions: where can I see what is the real difference between these two commands? Is it documented somewhere or should I go directly to the nix source code?

Another question that would help me a lot is the following: right now I build the flake and reference the output in a systemd unit

[Unit]
Description=WalletWasabi Backend API

[Service]
ExecStart=/home/theuser/result/bin/WalletWasabi.Backend

Does it make sense to do that or could I just have this:

[Unit]
Description=WalletWasabi Backend API

[Service]
ExecStart=nix run github:zkSNACKs/WalletWasabi/stable

There is basically none, that’s why the documentation mentions none.

You can look at the source code (the files are src/nix/build.cc and src/nix/run.cc), but run in particular does pretty much what it says:

  1. It builds the installable (via Installable::toStorePaths, which calls Installable::build, which is exactly what nix build calls)
  2. It adds the store paths of the built installable and its dependencies to the PATH within its own, isolated environment
  3. It runs an execvp syscall to replace itself with the downloaded binary (or whatever was passed with the -c option)

The last part is what makes the behavior you’re describing particularly puzzling. Once the target binary was started, the nix process does not exist anymore, so it can’t influence the execution of the program in any way. You can see this in the code:

execvp(program.c_str(), stringsToCharPtrs(args).data());

throw SysError("unable to execute '%s'", program);

From the point-of-view of nix run, the execvp call never finishes.

1 Like

Ah and about the systemd unit, I assume you’re not on NixOS? Using nix run there is fine, it might just increase the startup time of this unit significantly if an update is available.

nix run does work fine when executed as root, which is what systemd is doing for most units AFAIK.

But you’ll have to try it out. You may have to change the line to

ExecStart=nix run 'github:zkSNACKs/WalletWasabi/stable'

Or potentially even

ExecStart=bash -c "nix run 'github:zkSNACKs/WalletWasabi/stable'"

I vaguely remember having issues with multi-argument commands in the past.