TmuxFailure
LibTmux.FSharp Recognises tmux failures by whether running the operation again could repeat what it did.
Source and package
- Module
- LibTmux.FSharp
- Package
- LibTmux.FSharp
- Source
- src/LibTmux.FSharp/Library.fsi
- module Tmux
Failure -
Description#
Every <c>LibTmuxException</c> says whether its command reached tmux, so match on that rather than on the exception type. <c>NotSent</c> is the only failure after which running the same command again is always safe. It says nothing about commands sent before it: an operation that ran one command and then failed to send another has already acted. <c>Async.AwaitTask</c> and <c>Task.Wait</c> hand a failure over inside an <c>AggregateException</c>; one holding a single failure is matched as that failure.
Examples#
Set and read a typed option, then meet each kind of failure on a real server: a server that is not running (NotSent), a value tmux refuses (Ran), and a run cancelled after it was sent (MayHaveRun). Retry.ifNotSentAfter sends again only while nothing reached tmux. Use Linux or macOS with Git, tmux 3.2a or newer, .NET SDK 10.0.303, and the .NET 8 and 10 runtimes. In an empty directory, fetch the documented library revision:
Terminal window $ git clone https://github.com/libtmux/libtmux-dotnet.git libtmux-source && \git -C libtmux-source checkout --detach cf34b255c896d19a97e7d7fabea58609c916012dSave this complete consumer setup as global.json: View source
{"sdk": {"version": "10.0.303","rollForward": "disable","allowPrerelease": false},"test": {"runner": "Microsoft.Testing.Platform"}}Save this complete consumer setup as NuGet.config: View source
<?xml version="1.0" encoding="utf-8"?><configuration><packageSources><clear /><add key="libtmux-source" value="libtmux-source/artifacts/api-example-packages" /><add key="nuget.org" value="https://api.nuget.org/v3/index.json" /></packageSources><packageSourceMapping><clear /><packageSource key="libtmux-source"><package pattern="LibTmux" /><package pattern="LibTmux.*" /></packageSource><packageSource key="nuget.org"><package pattern="*" /></packageSource></packageSourceMapping></configuration>Save this complete consumer setup as Example.fsproj: View source
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFrameworks>net8.0;net10.0</TargetFrameworks><LangVersion>10.0</LangVersion><TreatWarningsAsErrors>true</TreatWarningsAsErrors><DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference><DisableImplicitLibraryPacksFolder>true</DisableImplicitLibraryPacksFolder></PropertyGroup><ItemGroup><Compile Include="Program.fs" /><PackageReference Include="FSharp.Core" Version="[10.1.303]" /><PackageReference Include="LibTmux.FSharp" Version="[0.0.0-alpha.20]" /></ItemGroup></Project>Save this complete program as Program.fs. It creates a private server with LibTmux.Server.CreateOwnedAsync; use! disposes owned scopes after success, cancellation or failure. The F# facade has no separate server constructor. View source
// fsharp-snippet: FailureKindsopen Systemopen System.Threadingopen System.Threading.Tasksopen LibTmuxopen LibTmux.FSharp// A failure says whether tmux saw the command, which decides whether sending// it again could repeat what it did.let kind (work: unit -> Task) =task {trydo! work ()return "succeeded"with| TmuxFailure.NotSent _ -> return "NotSent"| TmuxFailure.Ran _ -> return "Ran"| TmuxFailure.MayHaveRun _ -> return "MayHaveRun"}let runAsync () =task {use deadline = new CancellationTokenSource(TimeSpan.FromSeconds 20.)let token = deadline.Tokenlet socket = "fsharp-failure-kinds-" + Guid.NewGuid().ToString("N")let options =ServerConnectionOptions(SocketName = socket, ConfigurationFile = "/dev/null")use! owned = options |> Server.createOwned tokenlet! session =owned.Value |> Server.newSession token (SessionSpec.running "jobs" "/bin/sh")let! pane = session |> Session.activePane tokenlet! _ =pane |> Pane.sendAndWait token (TimeSpan.FromSeconds 5.) "echo ready" "ready"// Typed options read back as the type they were written with.do! session.Options |> Options.set token TmuxOptionKey.HistoryLimit 50_000let! history = session.Options |> Options.get token TmuxOptionKey.HistoryLimitprintfn "history-limit: %d" history// No server listens on this socket, so no command reached one.let missing =ServerConnectionOptions(SocketName = socket + "-none", ConfigurationFile = "/dev/null")let! notSent = kind (fun () -> missing |> Server.connect token :> Task)printfn "A server that is not running: %s" notSent// tmux ran the command and refused it.let refuse (cancellationToken: CancellationToken) =task { do! session.Options |> Options.set cancellationToken TmuxOptionKey.HistoryLimit -1 }let! ran = kind (fun () -> refuse token :> Task)printfn "A value tmux refuses: %s" ran// The command is running when the token is cancelled.use stop = CancellationTokenSource.CreateLinkedTokenSource(token)let running =pane |> Pane.run stop.Token (TimeSpan.FromSeconds 10.) "echo started; sleep 5"let! _ = pane |> Pane.waitForText token (TimeSpan.FromSeconds 5.) "started"stop.Cancel()let! mayHaveRun = kind (fun () -> running :> Task)printfn "A run cancelled after it was sent: %s" mayHaveRun// Retry sends again only while nothing reached tmux, as while a// server is still starting.let delays = [ TimeSpan.FromMilliseconds 10.; TimeSpan.FromMilliseconds 20. ]let attempts = ref 0let count (operation: CancellationToken -> Task<'T>) (cancellationToken: CancellationToken) =attempts.Value <- attempts.Value + 1operation cancellationTokenlet! unsent =kind (fun () -> Retry.ifNotSentAfter token delays (count (fun ct -> missing |> Server.connect ct)) :> Task)printfn "Retried while NotSent: %d attempts, then %s" attempts.Value unsentattempts.Value <- 0let! refused =kind (fun () -> Retry.ifNotSentAfter token delays (count refuse) :> Task)printfn "Retried after Ran: %d attempt, then %s" attempts.Value refused}// endfsharp-snippetrunAsync().GetAwaiter().GetResult()Build the exact source packages and the saved consumer, then run both supported frameworks. The local package feed prevents an unrelated published package from satisfying the restore. Each program checks its result and exits unsuccessfully after an unhandled error:
Terminal window $ export NUGET_PACKAGES="$PWD/packages" && \dotnet restore libtmux-source/src/LibTmux.FSharp/LibTmux.FSharp.fsproj --locked-mode && \dotnet pack libtmux-source/src/LibTmux/LibTmux.csproj \--configuration Release --no-restore -p:ContinuousIntegrationBuild=true \--output "$PWD/libtmux-source/artifacts/api-example-packages" && \dotnet pack libtmux-source/src/LibTmux.FSharp/LibTmux.FSharp.fsproj \--configuration Release --no-restore -p:ContinuousIntegrationBuild=true \--output "$PWD/libtmux-source/artifacts/api-example-packages" && \dotnet restore Example.fsproj --configfile NuGet.config && \dotnet build Example.fsproj --configuration Release --no-restore --warnaserror && \export LIBTMUX_TMUX="$(command -v tmux)" && \export TMUX_TMPDIR=/tmp/libtmux-dotnet-dev && \mkdir -p "$TMUX_TMPDIR" && \unset TMUX TMUX_PANE && \dotnet bin/Release/net8.0/Example.dll && \dotnet bin/Release/net10.0/Example.dllExpected program output from each framework:
history-limit: 50000A server that is not running: NotSentA value tmux refuses: RanA run cancelled after it was sent: MayHaveRunRetried while NotSent: 3 attempts, then NotSentRetried after Ran: 1 attempt, then RanDiscussed in Getting started , .NET interoperation
Members3 members#
Other members3 members#
- |MayHaveRun|_| function Matches a failure, or a cancellation, after which tmux may already have acted.
- |NotSent|_| function Matches a failure whose command never reached tmux; running it again repeats nothing.
- |Ran|_| function Matches a failure after tmux ran the command: tmux reported an error, or its answer could not be used.