Skip to content

Commit

Permalink
Refactor the way terminal handles are managed so the native library f…
Browse files Browse the repository at this point in the history
…ully abstracts them.
  • Loading branch information
alexrp committed Jan 8, 2024
1 parent a7e8535 commit 7eb23c7
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 182 deletions.
24 changes: 17 additions & 7 deletions src/core/Native/TerminalInterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ namespace Vezel.Cathode.Native;

internal static unsafe partial class TerminalInterop
{
[StructLayout(LayoutKind.Sequential)]
public struct TerminalDescriptor
{
}

public enum TerminalException
{
None,
Expand Down Expand Up @@ -98,19 +103,24 @@ bool TryLoad(out nint handle, params string[] paths)
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial void Initialize();

[LibraryImport(Library, EntryPoint = "cathode_get_handles")]
[LibraryImport(Library, EntryPoint = "cathode_get_descriptors")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial void GetHandles(nuint* stdIn, nuint* stdOut, nuint* stdErr, nuint* ttyIn, nuint* ttyOut);
public static partial void GetDescriptors(
TerminalDescriptor** stdIn,
TerminalDescriptor** stdOut,
TerminalDescriptor** stdErr,
TerminalDescriptor** ttyIn,
TerminalDescriptor** ttyOut);

[LibraryImport(Library, EntryPoint = "cathode_is_valid")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool IsValid(nuint handle, [MarshalAs(UnmanagedType.U1)] bool write);
public static partial bool IsValid(TerminalDescriptor* descriptor, [MarshalAs(UnmanagedType.U1)] bool write);

[LibraryImport(Library, EntryPoint = "cathode_is_interactive")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool IsInteractive(nuint handle);
public static partial bool IsInteractive(TerminalDescriptor* descriptor);

[LibraryImport(Library, EntryPoint = "cathode_query_size")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
Expand All @@ -133,13 +143,13 @@ public static partial TerminalResult SetMode(

[LibraryImport(Library, EntryPoint = "cathode_read")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial TerminalResult Read(nuint handle, byte* buffer, int length, int* progress);
public static partial TerminalResult Read(TerminalDescriptor* descriptor, byte* buffer, int length, int* progress);

[LibraryImport(Library, EntryPoint = "cathode_write")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial TerminalResult Write(nuint handle, byte* buffer, int length, int* progress);
public static partial TerminalResult Write(TerminalDescriptor* descriptor, byte* buffer, int length, int* progress);

[LibraryImport(Library, EntryPoint = "cathode_poll")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial void Poll([MarshalAs(UnmanagedType.U1)] bool write, nuint* handles, bool* results, int count);
public static partial void Poll([MarshalAs(UnmanagedType.U1)] bool write, int* fds, bool* results, int count);
}
26 changes: 13 additions & 13 deletions src/core/Terminals/NativeTerminalReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,47 @@

namespace Vezel.Cathode.Terminals;

internal sealed class NativeTerminalReader : TerminalReader
internal sealed unsafe class NativeTerminalReader : TerminalReader
{
// Note that the buffer size used affects how many characters the Windows console host will allow the user to type
// in a single line (in cooked mode).
private const int ReadBufferSize = 4096;

public NativeVirtualTerminal Terminal { get; }

public nuint Handle { get; }
public TerminalInterop.TerminalDescriptor* Descriptor { get; }

public override sealed Stream Stream { get; }
public override Stream Stream { get; }

public override sealed TextReader TextReader { get; }
public override TextReader TextReader { get; }

public override sealed bool IsValid { get; }
public override bool IsValid { get; }

public override sealed bool IsInteractive { get; }
public override bool IsInteractive { get; }

private readonly SemaphoreSlim _semaphore;

private readonly Action<nuint, CancellationToken>? _cancellationHook;

public NativeTerminalReader(
NativeVirtualTerminal terminal,
nuint handle,
TerminalInterop.TerminalDescriptor* descriptor,
SemaphoreSlim semaphore,
Action<nuint, CancellationToken>? cancellationHook)
{
Terminal = terminal;
Handle = handle;
Descriptor = descriptor;
_semaphore = semaphore;
_cancellationHook = cancellationHook;
Stream = new SynchronizedStream(new TerminalInputStream(this));
TextReader =
new SynchronizedTextReader(
new StreamReader(Stream, Cathode.Terminal.Encoding, false, ReadBufferSize, true));
IsValid = TerminalInterop.IsValid(handle, write: false);
IsInteractive = TerminalInterop.IsInteractive(handle);
IsValid = TerminalInterop.IsValid(descriptor, write: false);
IsInteractive = TerminalInterop.IsInteractive(descriptor);
}

private unsafe int ReadPartialNative(scoped Span<byte> buffer, CancellationToken cancellationToken)
private int ReadPartialNative(scoped Span<byte> buffer, CancellationToken cancellationToken)
{
using var guard = Terminal.Control.Guard();

Expand All @@ -53,12 +53,12 @@ private unsafe int ReadPartialNative(scoped Span<byte> buffer, CancellationToken

using (_semaphore.Enter(cancellationToken))
{
_cancellationHook?.Invoke(Handle, cancellationToken);
_cancellationHook?.Invoke((nuint)Descriptor, cancellationToken);

int progress;

fixed (byte* p = buffer)
TerminalInterop.Read(Handle, p, buffer.Length, &progress).ThrowIfError();
TerminalInterop.Read(Descriptor, p, buffer.Length, &progress).ThrowIfError();

return progress;
}
Expand Down
26 changes: 13 additions & 13 deletions src/core/Terminals/NativeTerminalWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@

namespace Vezel.Cathode.Terminals;

internal sealed class NativeTerminalWriter : TerminalWriter
internal sealed unsafe class NativeTerminalWriter : TerminalWriter
{
// Unlike NativeTerminalReader, the buffer size here is arbitrary and only has performance implications.
private const int WriteBufferSize = 256;

public NativeVirtualTerminal Terminal { get; }

public nuint Handle { get; }
public TerminalInterop.TerminalDescriptor* Descriptor { get; }

public override sealed Stream Stream { get; }
public override Stream Stream { get; }

public override sealed TextWriter TextWriter { get; }
public override TextWriter TextWriter { get; }

public override sealed bool IsValid { get; }
public override bool IsValid { get; }

public override sealed bool IsInteractive { get; }
public override bool IsInteractive { get; }

private readonly SemaphoreSlim _semaphore;

private readonly Action<nuint, CancellationToken>? _cancellationHook;

public NativeTerminalWriter(
NativeVirtualTerminal terminal,
nuint handle,
TerminalInterop.TerminalDescriptor* descriptor,
SemaphoreSlim semaphore,
Action<nuint, CancellationToken>? cancellationHook)
{
Terminal = terminal;
Handle = handle;
Descriptor = descriptor;
_semaphore = semaphore;
_cancellationHook = cancellationHook;
Stream = new SynchronizedStream(new TerminalOutputStream(this));
Expand All @@ -39,11 +39,11 @@ public NativeTerminalWriter(
{
AutoFlush = true,
});
IsValid = TerminalInterop.IsValid(handle, write: true);
IsInteractive = TerminalInterop.IsInteractive(handle);
IsValid = TerminalInterop.IsValid(descriptor, write: true);
IsInteractive = TerminalInterop.IsInteractive(descriptor);
}

private unsafe int WritePartialNative(scoped ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
private int WritePartialNative(scoped ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
{
using var guard = Terminal.Control.Guard();

Expand All @@ -54,12 +54,12 @@ private unsafe int WritePartialNative(scoped ReadOnlySpan<byte> buffer, Cancella

using (_semaphore.Enter(cancellationToken))
{
_cancellationHook?.Invoke(Handle, cancellationToken);
_cancellationHook?.Invoke((nuint)Descriptor, cancellationToken);

int progress;

fixed (byte* p = buffer)
TerminalInterop.Write(Handle, p, buffer.Length, &progress).ThrowIfError();
TerminalInterop.Write(Descriptor, p, buffer.Length, &progress).ThrowIfError();

return progress;
}
Expand Down
28 changes: 17 additions & 11 deletions src/core/Terminals/NativeVirtualTerminal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ internal abstract class NativeVirtualTerminal : SystemVirtualTerminal

public override sealed NativeTerminalWriter TerminalOut { get; }

[SuppressMessage("", "CA2000")]
[SuppressMessage("", "CA2214")]
private protected unsafe NativeVirtualTerminal()
{
// Ensure that the native library is fully loaded and initialized before we do anything terminal-related.
Expand All @@ -24,13 +22,23 @@ private protected unsafe NativeVirtualTerminal()
var inLock = new SemaphoreSlim(1, 1);
var outLock = new SemaphoreSlim(1, 1);

nuint stdIn;
nuint stdOut;
nuint stdErr;
nuint ttyIn;
nuint ttyOut;
TerminalInterop.TerminalDescriptor* stdIn;
TerminalInterop.TerminalDescriptor* stdOut;
TerminalInterop.TerminalDescriptor* stdErr;
TerminalInterop.TerminalDescriptor* ttyIn;
TerminalInterop.TerminalDescriptor* ttyOut;

TerminalInterop.GetHandles(&stdIn, &stdOut, &stdErr, &ttyIn, &ttyOut);
TerminalInterop.GetDescriptors(&stdIn, &stdOut, &stdErr, &ttyIn, &ttyOut);

NativeTerminalReader CreateReader(TerminalInterop.TerminalDescriptor* descriptor, SemaphoreSlim semaphore)
{
return new(this, descriptor, semaphore, CreateCancellationHook(write: false));
}

NativeTerminalWriter CreateWriter(TerminalInterop.TerminalDescriptor* descriptor, SemaphoreSlim semaphore)
{
return new(this, descriptor, semaphore, CreateCancellationHook(write: true));
}

StandardIn = CreateReader(stdIn, inLock);
StandardOut = CreateWriter(stdOut, outLock);
Expand All @@ -39,9 +47,7 @@ private protected unsafe NativeVirtualTerminal()
TerminalOut = CreateWriter(ttyOut, outLock);
}

protected abstract NativeTerminalReader CreateReader(nuint handle, SemaphoreSlim semaphore);

protected abstract NativeTerminalWriter CreateWriter(nuint handle, SemaphoreSlim semaphore);
protected abstract Action<nuint, CancellationToken>? CreateCancellationHook(bool write);

private protected override sealed unsafe Size? QuerySize()
{
Expand Down
8 changes: 5 additions & 3 deletions src/core/Terminals/UnixCancellationPipe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace Vezel.Cathode.Terminals;
[SuppressMessage("", "CA1001")]
internal sealed class UnixCancellationPipe
{
// TODO: Move this logic to src/native/driver-unix.c.

private readonly AnonymousPipeServerStream _server;

private readonly AnonymousPipeClientStream _client;
Expand All @@ -18,7 +20,7 @@ public UnixCancellationPipe(bool write)
_write = write;
}

public unsafe void PollWithCancellation(nuint handle, CancellationToken cancellationToken)
public unsafe void PollWithCancellation(int fd, CancellationToken cancellationToken)
{
// Note that the runtime sets up a SIGPIPE handler for us.

Expand All @@ -31,8 +33,8 @@ public unsafe void PollWithCancellation(nuint handle, CancellationToken cancella
{
var handles = stackalloc[]
{
(nuint)pipeHandle.DangerousGetHandle(),
handle,
(int)pipeHandle.DangerousGetHandle(),
fd,
};
var results = stackalloc bool[2];

Expand Down
20 changes: 7 additions & 13 deletions src/core/Terminals/UnixVirtualTerminal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@ internal sealed class UnixVirtualTerminal : NativeVirtualTerminal

public static UnixVirtualTerminal Instance { get; } = new();

[SuppressMessage("", "IDE0052")]
private readonly PosixSignalRegistration _sigWinch;

[SuppressMessage("", "IDE0052")]
private readonly PosixSignalRegistration _sigCont;

[SuppressMessage("", "IDE0052")]
private readonly PosixSignalRegistration _sigChld;

public unsafe UnixVirtualTerminal()
Expand Down Expand Up @@ -57,17 +54,14 @@ void HandleSignal(PosixSignalContext context)
_sigChld = PosixSignalRegistration.Create(PosixSignal.SIGCHLD, HandleSignal);
}

protected override NativeTerminalReader CreateReader(nuint handle, SemaphoreSlim semaphore)
protected override unsafe Action<nuint, CancellationToken> CreateCancellationHook(bool write)
{
var pipe = new UnixCancellationPipe(write: false);
var pipe = new UnixCancellationPipe(write);

return new(this, handle, semaphore, cancellationHook: pipe.PollWithCancellation);
}

protected override NativeTerminalWriter CreateWriter(nuint handle, SemaphoreSlim semaphore)
{
var pipe = new UnixCancellationPipe(write: true);

return new(this, handle, semaphore, cancellationHook: pipe.PollWithCancellation);
return (descriptor, cancellationToken) =>
{
if (cancellationToken.CanBeCanceled)
pipe.PollWithCancellation(*(int*)descriptor, cancellationToken);
};
}
}
9 changes: 2 additions & 7 deletions src/core/Terminals/WindowsVirtualTerminal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,8 @@ private WindowsVirtualTerminal()
{
}

protected override NativeTerminalReader CreateReader(nuint handle, SemaphoreSlim semaphore)
protected override unsafe Action<nuint, CancellationToken>? CreateCancellationHook(bool write)
{
return new(this, handle, semaphore, cancellationHook: null);
}

protected override NativeTerminalWriter CreateWriter(nuint handle, SemaphoreSlim semaphore)
{
return new(this, handle, semaphore, cancellationHook: null);
return null;
}
}
Loading

0 comments on commit 7eb23c7

Please sign in to comment.