|
@@ -1,164 +1,88 @@
|
|
|
using MHD;
|
|
using MHD;
|
|
|
-using Invercargill;
|
|
|
|
|
-using Invercargill.DataStructures;
|
|
|
|
|
|
|
|
|
|
namespace Astralis {
|
|
namespace Astralis {
|
|
|
|
|
|
|
|
- /// Monitors MHD's file descriptors and integrates them with GLib's main loop.
|
|
|
|
|
- /// This enables external select mode where MHD runs entirely within the GLib
|
|
|
|
|
|
|
+ /// Monitors MHD's epoll file descriptor and integrates it with GLib's main loop.
|
|
|
|
|
+ /// This enables external epoll mode where MHD runs entirely within the GLib
|
|
|
/// main loop, eliminating cross-thread communication issues with suspend/resume.
|
|
/// main loop, eliminating cross-thread communication issues with suspend/resume.
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// MHD is started with MHD_USE_EPOLL (no internal polling thread) and exposes
|
|
|
|
|
+ /// its epoll fd via MHD_get_daemon_info; a single GLib IO watch on that fd
|
|
|
|
|
+ /// drives MHD_run. Unlike the previous external-select integration this has
|
|
|
|
|
+ /// no FD_SETSIZE (1024) ceiling: select()'s fixed-size fd_set can neither
|
|
|
|
|
+ /// represent nor safely test descriptors >= 1024, which silently killed
|
|
|
|
|
+ /// long-running servers as soon as their fd table grew that high.
|
|
|
internal class SocketMonitor : Object {
|
|
internal class SocketMonitor : Object {
|
|
|
private unowned MHD.Daemon daemon;
|
|
private unowned MHD.Daemon daemon;
|
|
|
- private Dictionary<int, WatchInfo> watches;
|
|
|
|
|
|
|
+ private IOChannel? epoll_channel;
|
|
|
|
|
+ private uint epoll_watch_id;
|
|
|
private uint timeout_id;
|
|
private uint timeout_id;
|
|
|
private bool running;
|
|
private bool running;
|
|
|
-
|
|
|
|
|
- private class WatchInfo {
|
|
|
|
|
- public IOChannel channel;
|
|
|
|
|
- public uint watch_id;
|
|
|
|
|
- public bool monitor_read;
|
|
|
|
|
- public bool monitor_write;
|
|
|
|
|
-
|
|
|
|
|
- public WatchInfo(IOChannel channel, uint watch_id, bool monitor_read, bool monitor_write) {
|
|
|
|
|
- this.channel = channel;
|
|
|
|
|
- this.watch_id = watch_id;
|
|
|
|
|
- this.monitor_read = monitor_read;
|
|
|
|
|
- this.monitor_write = monitor_write;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
public SocketMonitor(MHD.Daemon daemon) {
|
|
public SocketMonitor(MHD.Daemon daemon) {
|
|
|
this.daemon = daemon;
|
|
this.daemon = daemon;
|
|
|
- this.watches = new Dictionary<int, WatchInfo>();
|
|
|
|
|
|
|
+ this.epoll_channel = null;
|
|
|
|
|
+ this.epoll_watch_id = 0;
|
|
|
this.timeout_id = 0;
|
|
this.timeout_id = 0;
|
|
|
this.running = false;
|
|
this.running = false;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
/// Start monitoring MHD sockets and integrate with GLib main loop
|
|
/// Start monitoring MHD sockets and integrate with GLib main loop
|
|
|
public void start() {
|
|
public void start() {
|
|
|
if (running) {
|
|
if (running) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ var info = MHD.get_daemon_info(daemon, MHD.DaemonInfoType.EPOLL_FD);
|
|
|
|
|
+ if (info == null || ((!)info).epoll_fd < 0) {
|
|
|
|
|
+ error("Astralis: MHD daemon does not expose an epoll fd (external epoll mode is required on this platform)");
|
|
|
|
|
+ }
|
|
|
|
|
+ int epoll_fd = ((!)info).epoll_fd;
|
|
|
|
|
+
|
|
|
|
|
+ // The IOChannel does not own the fd; MHD keeps it open for the
|
|
|
|
|
+ // daemon's lifetime, and the watch holds a reference to the channel.
|
|
|
|
|
+ epoll_channel = new IOChannel.unix_new(epoll_fd);
|
|
|
|
|
+ IOCondition cond = IOCondition.IN | IOCondition.HUP | IOCondition.ERR | IOCondition.NVAL;
|
|
|
|
|
+ epoll_watch_id = epoll_channel.add_watch(cond, (source, condition) => {
|
|
|
|
|
+ if (!running) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Run MHD processing
|
|
|
|
|
+ MHD.run(daemon);
|
|
|
|
|
+
|
|
|
|
|
+ // Reschedule timeout if MHD requested a new one
|
|
|
|
|
+ schedule_next_timeout();
|
|
|
|
|
+
|
|
|
|
|
+ // Continue monitoring
|
|
|
|
|
+ return true;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
running = true;
|
|
running = true;
|
|
|
- update_watches();
|
|
|
|
|
schedule_next_timeout();
|
|
schedule_next_timeout();
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
/// Stop monitoring and clean up all GLib sources
|
|
/// Stop monitoring and clean up all GLib sources
|
|
|
public void stop() {
|
|
public void stop() {
|
|
|
if (!running) {
|
|
if (!running) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
running = false;
|
|
running = false;
|
|
|
-
|
|
|
|
|
- // Remove all IO channel watches
|
|
|
|
|
- foreach (var entry in watches) {
|
|
|
|
|
- Source.remove(entry.value.watch_id);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (epoll_watch_id != 0) {
|
|
|
|
|
+ Source.remove(epoll_watch_id);
|
|
|
|
|
+ epoll_watch_id = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (epoll_channel != null) {
|
|
|
|
|
+ epoll_channel = null;
|
|
|
}
|
|
}
|
|
|
- watches.clear();
|
|
|
|
|
-
|
|
|
|
|
- // Remove timeout watcher
|
|
|
|
|
|
|
+
|
|
|
if (timeout_id != 0) {
|
|
if (timeout_id != 0) {
|
|
|
Source.remove(timeout_id);
|
|
Source.remove(timeout_id);
|
|
|
timeout_id = 0;
|
|
timeout_id = 0;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- /// Update GLib IO channel watches based on MHD's current fd_set
|
|
|
|
|
- private void update_watches() {
|
|
|
|
|
- if (!running) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Get current fd_set from MHD
|
|
|
|
|
- MHD.FDSet read_fds = MHD.FDSet();
|
|
|
|
|
- MHD.FDSet write_fds = MHD.FDSet();
|
|
|
|
|
- MHD.FDSet error_fds = MHD.FDSet();
|
|
|
|
|
- int max_fd = 0;
|
|
|
|
|
-
|
|
|
|
|
- MHD.FDSet.zero(ref read_fds);
|
|
|
|
|
- MHD.FDSet.zero(ref write_fds);
|
|
|
|
|
- MHD.FDSet.zero(ref error_fds);
|
|
|
|
|
-
|
|
|
|
|
- MHD.get_fdset(daemon, ref read_fds, ref write_fds, ref error_fds, &max_fd);
|
|
|
|
|
-
|
|
|
|
|
- // Build set of active file descriptors
|
|
|
|
|
- var active_fds = new Series<int>();
|
|
|
|
|
- for (int fd = 0; fd <= max_fd; fd++) {
|
|
|
|
|
- bool needs_read = MHD.FDSet.is_set(fd, ref read_fds);
|
|
|
|
|
- bool needs_write = MHD.FDSet.is_set(fd, ref write_fds);
|
|
|
|
|
-
|
|
|
|
|
- if (needs_read || needs_write) {
|
|
|
|
|
- active_fds.add(fd);
|
|
|
|
|
-
|
|
|
|
|
- // Add new watch if not already monitoring this fd, or replace it if conditions changed
|
|
|
|
|
- WatchInfo? existing_watch = null;
|
|
|
|
|
- if (watches.try_get(fd, out existing_watch)) {
|
|
|
|
|
- if (existing_watch.monitor_read != needs_read || existing_watch.monitor_write != needs_write) {
|
|
|
|
|
- remove_watch_for_fd(fd);
|
|
|
|
|
- add_watch_for_fd(fd, needs_read, needs_write);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- add_watch_for_fd(fd, needs_read, needs_write);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Remove watches for closed file descriptors
|
|
|
|
|
- var to_remove = new Series<int>();
|
|
|
|
|
- foreach (var entry in watches) {
|
|
|
|
|
- if (!active_fds.contains(entry.key)) {
|
|
|
|
|
- to_remove.add(entry.key);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- foreach (int fd in to_remove) {
|
|
|
|
|
- remove_watch_for_fd(fd);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// Add a GLib IO channel watch for a file descriptor
|
|
|
|
|
- private void add_watch_for_fd(int fd, bool monitor_read, bool monitor_write) {
|
|
|
|
|
- var channel = new IOChannel.unix_new(fd);
|
|
|
|
|
-
|
|
|
|
|
- // Build IO condition flags - always monitor for errors/hangup
|
|
|
|
|
- IOCondition cond = IOCondition.HUP | IOCondition.ERR | IOCondition.NVAL;
|
|
|
|
|
- if (monitor_read) {
|
|
|
|
|
- cond |= IOCondition.IN;
|
|
|
|
|
- }
|
|
|
|
|
- if (monitor_write) {
|
|
|
|
|
- cond |= IOCondition.OUT;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- uint watch_id = channel.add_watch(cond, (source, condition) => {
|
|
|
|
|
- if (!running) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Run MHD processing
|
|
|
|
|
- MHD.run(daemon);
|
|
|
|
|
-
|
|
|
|
|
- // Socket set may have changed after processing
|
|
|
|
|
- update_watches();
|
|
|
|
|
-
|
|
|
|
|
- // Reschedule timeout if MHD requested a new one
|
|
|
|
|
- schedule_next_timeout();
|
|
|
|
|
-
|
|
|
|
|
- // Continue monitoring
|
|
|
|
|
- return true;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- watches[fd] = new WatchInfo(channel, watch_id, monitor_read, monitor_write);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// Remove a GLib IO channel watch for a file descriptor
|
|
|
|
|
- private void remove_watch_for_fd(int fd) {
|
|
|
|
|
- WatchInfo? info = null;
|
|
|
|
|
- if (watches.try_get(fd, out info)) {
|
|
|
|
|
- Source.remove(info.watch_id);
|
|
|
|
|
- watches.remove(fd);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
private bool update_pending = false;
|
|
private bool update_pending = false;
|
|
|
|
|
|
|
|
public void force_update() {
|
|
public void force_update() {
|
|
@@ -166,37 +90,34 @@ namespace Astralis {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
update_pending = true;
|
|
update_pending = true;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Idle.add(() => {
|
|
Idle.add(() => {
|
|
|
update_pending = false;
|
|
update_pending = false;
|
|
|
if (!running) {
|
|
if (!running) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// Run MHD processing
|
|
// Run MHD processing
|
|
|
MHD.run(daemon);
|
|
MHD.run(daemon);
|
|
|
-
|
|
|
|
|
- // Socket set may have changed
|
|
|
|
|
- update_watches();
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// Reschedule timeout
|
|
// Reschedule timeout
|
|
|
schedule_next_timeout();
|
|
schedule_next_timeout();
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return false;
|
|
return false;
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
/// Schedule the next timeout callback using MHD's recommended timeout
|
|
/// Schedule the next timeout callback using MHD's recommended timeout
|
|
|
private void schedule_next_timeout() {
|
|
private void schedule_next_timeout() {
|
|
|
if (!running) {
|
|
if (!running) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (timeout_id != 0) {
|
|
if (timeout_id != 0) {
|
|
|
Source.remove(timeout_id);
|
|
Source.remove(timeout_id);
|
|
|
timeout_id = 0;
|
|
timeout_id = 0;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
uint64 timeout_ms = 0;
|
|
uint64 timeout_ms = 0;
|
|
|
if (MHD.get_timeout(daemon, &timeout_ms) == MHD.Result.YES) {
|
|
if (MHD.get_timeout(daemon, &timeout_ms) == MHD.Result.YES) {
|
|
|
if (timeout_ms == 0) {
|
|
if (timeout_ms == 0) {
|
|
@@ -206,7 +127,6 @@ namespace Astralis {
|
|
|
}
|
|
}
|
|
|
timeout_id = 0;
|
|
timeout_id = 0;
|
|
|
MHD.run(daemon);
|
|
MHD.run(daemon);
|
|
|
- update_watches();
|
|
|
|
|
schedule_next_timeout();
|
|
schedule_next_timeout();
|
|
|
return false; // run once, reschedule later
|
|
return false; // run once, reschedule later
|
|
|
});
|
|
});
|
|
@@ -217,7 +137,6 @@ namespace Astralis {
|
|
|
}
|
|
}
|
|
|
timeout_id = 0;
|
|
timeout_id = 0;
|
|
|
MHD.run(daemon);
|
|
MHD.run(daemon);
|
|
|
- update_watches();
|
|
|
|
|
schedule_next_timeout();
|
|
schedule_next_timeout();
|
|
|
// Don't auto-repeat - we reschedule manually with potentially new timeout
|
|
// Don't auto-repeat - we reschedule manually with potentially new timeout
|
|
|
return false;
|
|
return false;
|