Browse Source

Use epoll for dispatcher loop since it's more accurate with wait timeouts (#17123)

* Use epoll for dispatcher loop since it's more accurate with wait timeouts

* review comments
pull/17180/head
Nikita Tsukanov 2 years ago
committed by GitHub
parent
commit
4a06d75881
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 235
      src/Linux/Avalonia.LinuxFramebuffer/EpollDispatcherImpl.cs
  2. 2
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

235
src/Linux/Avalonia.LinuxFramebuffer/EpollDispatcherImpl.cs

@ -0,0 +1,235 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Controls.Platform;
using Avalonia.Threading;
namespace Avalonia.LinuxFramebuffer;
internal unsafe class EpollDispatcherImpl : IControlledDispatcherImpl
{
private readonly ManagedDispatcherImpl.IManagedDispatcherInputProvider _inputProvider;
private Thread _mainThread;
[StructLayout(LayoutKind.Explicit)]
private struct epoll_data
{
[FieldOffset(0)] public IntPtr ptr;
[FieldOffset(0)] public int fd;
[FieldOffset(0)] public uint u32;
[FieldOffset(0)] public ulong u64;
}
private const int CLOCK_MONOTONIC = 1;
private const int EPOLLIN = 1;
private const int EPOLL_CTL_ADD = 1;
private const int O_NONBLOCK = 2048;
private const int O_CLOEXEC = 0x80000;
private const int EPOLL_CLOEXEC = 0x80000;
[StructLayout(LayoutKind.Sequential)]
private struct epoll_event
{
public uint events;
public epoll_data data;
}
[DllImport("libc")]
private extern static int epoll_create1(int flags);
[DllImport("libc")]
private extern static int epoll_ctl(int epfd, int op, int fd, ref epoll_event __event);
[DllImport("libc")]
private extern static int epoll_wait(int epfd, epoll_event* events, int maxevents, int timeout);
[DllImport("libc")]
private extern static int pipe2(int* fds, int flags);
[DllImport("libc")]
private extern static IntPtr write(int fd, void* buf, IntPtr count);
[DllImport("libc")]
private extern static IntPtr read(int fd, void* buf, IntPtr count);
struct timespec
{
public IntPtr tv_sec;
public IntPtr tv_nsec;
}
struct itimerspec
{
public timespec it_interval; // Interval for periodic timer
public timespec it_value; // Initial expiration
};
[DllImport("libc")]
private extern static int timerfd_create(int clockid, int flags);
[DllImport("libc")]
private extern static int timerfd_settime(int fd, int flags, itimerspec* new_value, itimerspec* old_value);
private enum EventCodes
{
Timer = 1,
Signal = 2
}
private int _sigread, _sigwrite;
private int _timerfd;
private object _lock = new();
private bool _signaled;
private bool _wakeupRequested;
private TimeSpan? _nextTimer;
private int _epoll;
private Stopwatch _clock = Stopwatch.StartNew();
public EpollDispatcherImpl(ManagedDispatcherImpl.IManagedDispatcherInputProvider inputProvider)
{
_inputProvider = inputProvider;
_mainThread = Thread.CurrentThread;
_epoll = epoll_create1(EPOLL_CLOEXEC);
if (_epoll == -1)
throw new Win32Exception("epoll_create1 failed");
var fds = stackalloc int[2];
pipe2(fds, O_NONBLOCK | O_CLOEXEC);
_sigread = fds[0];
_sigwrite = fds[1];
var ev = new epoll_event
{
events = EPOLLIN,
data = { u32 = (int)EventCodes.Signal }
};
if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _sigread, ref ev) == -1)
throw new Win32Exception("Unable to attach signal pipe to epoll");
_timerfd = timerfd_create(CLOCK_MONOTONIC, O_NONBLOCK | O_CLOEXEC);
ev.data.u32 = (int)EventCodes.Timer;
if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _timerfd, ref ev) == -1)
throw new Win32Exception("Unable to attach timer fd to epoll");
}
private bool CheckSignaled()
{
lock (_lock)
{
if (!_signaled)
return false;
_signaled = false;
}
Signaled?.Invoke();
return true;
}
public void RunLoop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var now = _clock.Elapsed;
if (_nextTimer.HasValue && now > _nextTimer.Value)
{
Timer?.Invoke();
continue;
}
if (CheckSignaled())
continue;
if (_inputProvider.HasInput)
{
_inputProvider.DispatchNextInputEvent();
continue;
}
epoll_event ev;
if (_nextTimer != null)
{
var waitFor = _nextTimer.Value - now;
if (waitFor.Ticks < 0)
continue;
itimerspec timer = new()
{
it_value = new()
{
tv_sec = new IntPtr(Math.Min((int)waitFor.TotalSeconds, 100)),
tv_nsec = new IntPtr((waitFor.Ticks % 10000000) * 100)
}
};
timerfd_settime(_timerfd, 0, &timer, null);
}
else
{
itimerspec none = default;
timerfd_settime(_timerfd, 0, &none, null);
}
epoll_wait(_epoll, &ev, 1, (int)-1);
// Drain the signaled pipe
long buf = 0;
while (read(_sigread, &buf, new IntPtr(8)).ToInt64() > 0)
{
}
// Drain timer fd
while (read(_timerfd, &buf, new IntPtr(8)).ToInt64() > 0)
{
}
lock (_lock)
_wakeupRequested = false;
}
}
private void Wakeup()
{
lock (_lock)
{
if (_wakeupRequested)
return;
_wakeupRequested = true;
int buf = 0;
write(_sigwrite, &buf, new IntPtr(1));
}
}
public void Signal()
{
lock (_lock)
{
if (_signaled)
return;
_signaled = true;
Wakeup();
}
}
public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _mainThread;
public event Action Signaled;
public event Action Timer;
public void UpdateTimer(long? dueTimeInMs)
{
_nextTimer = dueTimeInMs == null ? null : TimeSpan.FromMilliseconds(dueTimeInMs.Value);
if (_nextTimer != null)
Wakeup();
}
public long Now => _clock.ElapsedMilliseconds;
public bool CanQueryPendingInput => true;
public bool HasPendingInput => _inputProvider.HasInput;
}

2
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -67,7 +67,7 @@ namespace Avalonia.LinuxFramebuffer
: new DefaultRenderTimer(opts.Fps);
AvaloniaLocator.CurrentMutable
.Bind<IDispatcherImpl>().ToConstant(new ManagedDispatcherImpl(new ManualRawEventGrouperDispatchQueueDispatcherInputProvider(EventGrouperDispatchQueue)))
.Bind<IDispatcherImpl>().ToConstant(new EpollDispatcherImpl(new ManualRawEventGrouperDispatchQueueDispatcherInputProvider(EventGrouperDispatchQueue)))
.Bind<IRenderTimer>().ToConstant(timer)
.Bind<ICursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())

Loading…
Cancel
Save