From 45f82ad62c407302c22fefb4760bba4d94380594 Mon Sep 17 00:00:00 2001 From: Brycen G Date: Thu, 7 Aug 2025 14:33:32 -0400 Subject: [PATCH 1/2] fix(X11): use select instead of epoll for FreeBSD support --- .../Dispatching/X11PlatformThreading.cs | 116 ++++++++---------- 1 file changed, 54 insertions(+), 62 deletions(-) diff --git a/src/Avalonia.X11/Dispatching/X11PlatformThreading.cs b/src/Avalonia.X11/Dispatching/X11PlatformThreading.cs index b071e405fd..8794a97ab1 100644 --- a/src/Avalonia.X11/Dispatching/X11PlatformThreading.cs +++ b/src/Avalonia.X11/Dispatching/X11PlatformThreading.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; -using Avalonia.Platform; using Avalonia.Threading; using Avalonia.X11.Dispatching; using static Avalonia.X11.XLib; @@ -15,59 +13,65 @@ namespace Avalonia.X11 private readonly AvaloniaX11Platform _platform; private Thread _mainThread = Thread.CurrentThread; - [StructLayout(LayoutKind.Explicit)] - private struct epoll_data + [StructLayout(LayoutKind.Sequential)] + public struct fd_set { - [FieldOffset(0)] - public IntPtr ptr; - [FieldOffset(0)] - public int fd; - [FieldOffset(0)] - public uint u32; - [FieldOffset(0)] - public ulong u64; + public const int FD_SETSIZE = 64; + public fixed uint fds[FD_SETSIZE]; + + public void Set(int fd) + { + var idx = fd / 32; + if (idx >= FD_SETSIZE) + throw new ArgumentException(); + var bit = (fd % 32); + fds[idx] |= 1u << bit; + } } - private const int EPOLLIN = 1; - private const int EPOLL_CTL_ADD = 1; - private const int O_NONBLOCK = 2048; - [StructLayout(LayoutKind.Sequential)] - private struct epoll_event + public struct timeval { - public uint events; - public epoll_data data; + public int tv_sec; + public int tv_usec; + + public timeval(int milliseconds) + { + tv_sec = milliseconds / 1000; + tv_usec = (milliseconds % 1000) * 1000; + } } - - [DllImport("libc")] - private extern static int epoll_create1(int size); - [DllImport("libc")] - private extern static int epoll_ctl(int epfd, int op, int fd, ref epoll_event __event); + private const int O_NONBLOCK = 2048; - [DllImport("libc")] - private extern static int epoll_wait(int epfd, epoll_event* events, int maxevents, int timeout); + [DllImport("libc", EntryPoint = "select")] + private static extern int select( + int nfds, + ref fd_set readfds, + ref fd_set writefds, + ref fd_set exceptfds, + timeval* 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); private enum EventCodes { X11 = 1, - Signal =2 + Signal = 2 } - private int _sigread, _sigwrite; + private readonly int _sigread, _sigwrite, _x11Fd; private object _lock = new object(); private bool _signaled; private bool _wakeupRequested; private long? _nextTimer; - private int _epoll; private Stopwatch _clock = Stopwatch.StartNew(); private readonly X11EventDispatcher _x11Events; @@ -75,30 +79,12 @@ namespace Avalonia.X11 { _platform = platform; _x11Events = new X11EventDispatcher(platform); - var ev = new epoll_event() - { - events = EPOLLIN, - data = {u32 = (int)EventCodes.X11} - }; - _epoll = epoll_create1(0); - if (_epoll == -1) - throw new X11Exception("epoll_create1 failed"); - - if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _x11Events.Fd, ref ev) == -1) - throw new X11Exception("Unable to attach X11 connection handle to epoll"); + _x11Fd = XConnectionNumber(platform.Display); var fds = stackalloc int[2]; pipe2(fds, O_NONBLOCK); _sigread = fds[0]; _sigwrite = fds[1]; - - ev = new epoll_event - { - events = EPOLLIN, - data = {u32 = (int)EventCodes.Signal} - }; - if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _sigread, ref ev) == -1) - throw new X11Exception("Unable to attach signal pipe to epoll"); } private void CheckSignaled() @@ -112,7 +98,6 @@ namespace Avalonia.X11 Signaled?.Invoke(); } - public void RunLoop(CancellationToken cancellationToken) { @@ -126,20 +111,27 @@ namespace Avalonia.X11 if (cancellationToken.IsCancellationRequested) return; - - //Flush whatever requests were made to XServer + _x11Events.Flush(); - epoll_event ev; + if (!_x11Events.IsPending) { now = _clock.ElapsedMilliseconds; if (_nextTimer < now) continue; - - var timeout = _nextTimer == null ? (int)-1 : Math.Max(1, _nextTimer.Value - now); - epoll_wait(_epoll, &ev, 1, (int)Math.Min(int.MaxValue, timeout)); - - // Drain the signaled pipe + + fd_set readFds = default, writeFds = default, exceptFds = default; + readFds.Set(_x11Fd); + readFds.Set(_sigread); + + var timeoutMs = _nextTimer == null ? (int?)null : Math.Max(1, (int)(_nextTimer.Value - now)); + var timeout = timeoutMs.HasValue ? new timeval(timeoutMs.Value) : default; + select(Math.Max(_x11Fd, _sigread) + 1, + ref readFds, + ref writeFds, + ref exceptFds, + timeoutMs.HasValue ? &timeout : null); + int buf = 0; while (read(_sigread, &buf, new IntPtr(4)).ToInt64() > 0) { @@ -151,6 +143,7 @@ namespace Avalonia.X11 if (cancellationToken.IsCancellationRequested) return; + CheckSignaled(); _x11Events.DispatchX11Events(cancellationToken); while (_platform.EventGrouperDispatchQueue.HasJobs) @@ -165,7 +158,7 @@ namespace Avalonia.X11 { lock (_lock) { - if(_wakeupRequested) + if (_wakeupRequested) return; _wakeupRequested = true; int buf = 0; @@ -177,7 +170,7 @@ namespace Avalonia.X11 { lock (_lock) { - if(_signaled) + if (_signaled) return; _signaled = true; Wakeup(); @@ -185,7 +178,7 @@ namespace Avalonia.X11 } public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _mainThread; - + public event Action? Signaled; public event Action? Timer; @@ -196,7 +189,6 @@ namespace Avalonia.X11 Wakeup(); } - public long Now => _clock.ElapsedMilliseconds; public bool CanQueryPendingInput => true; From c81e8c62cea4691073fd4fd375e9fc59543e4292 Mon Sep 17 00:00:00 2001 From: Brycen G Date: Fri, 8 Aug 2025 17:45:34 -0400 Subject: [PATCH 2/2] fix: increase FD_SETSIZE to 1024 and adjust bitset size --- .../Dispatching/X11PlatformThreading.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.X11/Dispatching/X11PlatformThreading.cs b/src/Avalonia.X11/Dispatching/X11PlatformThreading.cs index 8794a97ab1..e24eec7748 100644 --- a/src/Avalonia.X11/Dispatching/X11PlatformThreading.cs +++ b/src/Avalonia.X11/Dispatching/X11PlatformThreading.cs @@ -16,15 +16,15 @@ namespace Avalonia.X11 [StructLayout(LayoutKind.Sequential)] public struct fd_set { - public const int FD_SETSIZE = 64; - public fixed uint fds[FD_SETSIZE]; + public const int FD_SETSIZE = 1024; + public fixed uint fds[FD_SETSIZE / 32]; public void Set(int fd) { var idx = fd / 32; - if (idx >= FD_SETSIZE) - throw new ArgumentException(); - var bit = (fd % 32); + if (idx >= FD_SETSIZE / 32) + throw new ArgumentOutOfRangeException(nameof(fd)); + var bit = fd % 32; fds[idx] |= 1u << bit; } } @@ -61,12 +61,6 @@ namespace Avalonia.X11 [DllImport("libc")] private extern static IntPtr read(int fd, void* buf, IntPtr count); - private enum EventCodes - { - X11 = 1, - Signal = 2 - } - private readonly int _sigread, _sigwrite, _x11Fd; private object _lock = new object(); private bool _signaled;