Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@gdamore
Copy link
Owner

@gdamore gdamore commented Dec 10, 2025

…xes #8)

This now uses a new api for obtaining events, which allows the application to run in a single event loop without any locks or threading. Once small consequence of this is that if the application wants to work in an MT way with the screen, then it will need to provide its own guards.

This design means that this can now be used in designs that may not be conducive to using runtime threads (such as reactor designs found in Weka's mecca library), and it also should make debugging much simpler.

Summary by CodeRabbit

  • Refactor

    • Replaced thread/queue-based input with a polling/wait-style event API (waitEvent) and simplified start/stop lifecycle; removed legacy queue/turnstile internals.
    • Unified terminal I/O for duration-aware reads and cross-platform resize signaling.
  • Demos

    • Updated sample apps to use the new start/wait event patterns and removed legacy concurrency calls.
  • Documentation

    • Minor parser comment added and copyright year updated.
  • Chores

    • Removed obsolete internal modules and unused imports.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 10, 2025

Walkthrough

Threaded event queues and turnstiles were removed; Screen API changed from receiveEvent to waitEvent. TtyScreen now polls TtyImpl.read with a Parser-driven buffer and uses a signal pipe for resize wakeups. Demos and termio updated to use the new waitEvent/start semantics.

Changes

Cohort / File(s) Summary
Demo updates
demos/colors/source/colors.d, demos/hello/source/hello.d, demos/mouse/source/mouse.d
Removed std.concurrency usage; replaced receive/receiveEvent loops with waitEvent() calls; updated startup to call start() (no Tid) and removed per-thread start patterns.
Removed sync primitives
source/dcell/evqueue.d, source/dcell/turnstile.d
Deleted EventQueue and Turnstile modules, their APIs, private fields, and unit tests; removed send/receive/close/get/set/wait synchronization primitives.
Screen interface
source/dcell/screen.d
Replaced receiveEvent(Duration)/receiveEvent() with waitEvent(Duration dur = msecs(100)); removed start(Tid) overload and module-level std.concurrency dependency; docs updated.
Terminal I/O
source/dcell/termio.d
Changed TtyImpl.read() to read(Duration dur = Duration.zero); added select-based, duration-aware reads and sigPipe (sigPipe/sigRfd/sigWfd) signaling for resize wakeups; added platform/version branches and non-blocking FD handling.
TtyScreen implementation
source/dcell/ttyscreen.d
Removed EventQueue/Turnstile and threaded inputLoop; added Event[] events buffer and Parser parser; changed start() to parameterless; implemented polling waitEvent() that integrates parser-driven parsing and resize signaling.
Parser comment
source/dcell/parser.d
Removed a private keyCodes declaration and added a clarifying comment above parse(); no exported API changes.

Sequence Diagram(s)

sequenceDiagram
    participant App as Demo / App
    participant Screen as TtyScreen.waitEvent
    participant TTY as TtyImpl.read
    participant Parser as Parser.parse
    participant OS as OS (select/poll)

    App->>Screen: waitEvent(dur)
    Screen->>TTY: read(dur)
    TTY->>OS: select/poll(fd, dur)
    alt data available
        OS-->>TTY: bytes
        TTY-->>Parser: parse(bytes)
        Parser-->>Screen: produce Event(s)
        Screen-->>App: return Event
    else timeout or none
        OS-->>TTY: timeout/no data
        Screen-->>App: return none/timeout
    end
    Note over OS,TTY: resize signal writes to sigPipe -> wakes select/poll
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on source/dcell/ttyscreen.d (parser integration, event buffering, wait logic) and source/dcell/termio.d (sigPipe lifecycle, select/poll branches, cross-platform conditionals).
  • Verify all references to removed modules (evqueue.d, turnstile.d) are eliminated and callers updated (demos, Screen interface).
  • Check API signature changes (start(), waitEvent(), TtyImpl.read) are consistently propagated.

Possibly related PRs

Poem

🐰
Threads unspooled, I nibble at the key,
waitEvent listens where queues used to be;
A parser hums, a tiny pipe taps light,
Resizes wake the read from quiet night;
I hop, I code — a carrot-sized delight.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Completely refactor termio processing and eliminate threads' accurately describes the main objective—removing runtime threads and refactoring event handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch gdamore/nothread

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
source/dcell/ttyscreen.d (2)

126-221: Constructor/parser init and start() refactor look correct; consider clearing parser/events on reuse

Creating parser in the constructor and making start() idempotent while only manipulating terminal state aligns well with the new threadless design. The added events buffer plus started flag give you a clean, single-threaded lifecycle.

One small, non-blocking suggestion: if a TtyScreen instance is ever stop()ped and later start()ed again, any residual parser state and queued events from the previous run would carry over. If you want to support that reuse pattern robustly, consider clearing events and resetting/replacing parser in start() or stop() to guarantee a fresh input state per session.

Also applies to: 228-245, 469-471


19-20: waitEvent() timeout and polling logic are sound; only minor readability tweaks worth considering

The new waitEvent(Duration dur = msecs(100)) implementation correctly:

  • Accumulates events from parser.events() into a local events queue.
  • Synthesizes resize events when ti.resized() is true.
  • Uses MonoTime to enforce a timeout when dur is non‑negative, and a blocking ti.read() path when dur is negative.
  • Uses the residual flag plus a short msecs(1) interval to give the parser a chance to flush incomplete escape sequences (e.g., lone ESC) without busy‑looping.

Functionally this looks solid for both finite and “infinite” waits.

If you want, you could make two small readability improvements:

  • Only compute/use expire when !dur.isNegative, so it’s obvious the MonoTime.max sentinel is purely defensive.
  • Add a brief comment on the expected TtyImpl.resized() semantics (edge‑triggered vs level‑triggered), since you append a resize event whenever it returns true; that will help future readers reason about potential duplicate resize events.

These are cosmetic; no changes are required for correctness.

Also applies to: 392-445

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 030b9d7 and 899da76.

📒 Files selected for processing (9)
  • demos/colors/source/colors.d (1 hunks)
  • demos/hello/source/hello.d (2 hunks)
  • demos/mouse/source/mouse.d (2 hunks)
  • source/dcell/evqueue.d (0 hunks)
  • source/dcell/parser.d (1 hunks)
  • source/dcell/screen.d (2 hunks)
  • source/dcell/termio.d (9 hunks)
  • source/dcell/ttyscreen.d (6 hunks)
  • source/dcell/turnstile.d (0 hunks)
💤 Files with no reviewable changes (2)
  • source/dcell/evqueue.d
  • source/dcell/turnstile.d
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-08T19:10:45.974Z
Learnt from: gdamore
Repo: gdamore/dcell PR: 27
File: source/dcell/ttyscreen.d:763-766
Timestamp: 2025-12-08T19:10:45.974Z
Learning: In source/dcell/ttyscreen.d, the newTtyScreen() factory function intentionally does not accept a term parameter because the TtyScreen constructor handles environment variable resolution internally with an optional term parameter. Users needing to override the terminal type should call the constructor directly: new TtyScreen(newDevTty(), "custom-term").

Applied to files:

  • source/dcell/termio.d
  • demos/hello/source/hello.d
  • source/dcell/ttyscreen.d
🔇 Additional comments (13)
demos/colors/source/colors.d (1)

139-139: LGTM!

The migration from receiveEvent to waitEvent is correct. The 50ms timeout is appropriate for the animation refresh rate in this demo.

demos/mouse/source/mouse.d (2)

107-107: LGTM!

Correctly migrated from start(thisTid()) to the simplified start() API.


142-142: LGTM!

The migration to waitEvent() (with default 100ms timeout) is appropriate for this interactive demo's event loop.

source/dcell/screen.d (2)

96-100: LGTM!

The new waitEvent API with a default 100ms timeout is a clean replacement for the previous receiveEvent methods. The documentation correctly notes that this should be called frequently to rescan for changes.


203-215: LGTM!

The updated start() and stop() documentation accurately reflects the simplified lifecycle without threading concerns.

source/dcell/termio.d (4)

19-34: LGTM!

The version blocks correctly identify Apple platforms that need the select()-based implementation due to poll() not working properly on /dev/tty.


77-77: LGTM!

The updated interface signature with Duration dur = Duration.zero provides a clean API for timed reads while maintaining backward compatibility with the default.


411-426: LGTM on the signal pipe mechanism.

The pipe-based signaling for resize notifications is a clean solution for waking up the reader thread-safely from a signal handler.


454-472: LGTM!

The pipe initialization in watchResize correctly sets both ends to non-blocking mode, which is essential for safe signal handler interaction.

demos/hello/source/hello.d (4)

20-27: emitStr implementation is clear and matches its ASCII-only intent

The byte-wise loop over str[0] with the explicit “assumes only ASCII” comment aligns with how you use it in this demo and keeps the function straightforward. No issues here.


31-47: displayHelloWorld correctly sets up styles and content for the demo

Using a default background/foreground style, then drawing the two instructional strings and the centered red “X” gives a nice visual demonstration of the API. The function’s control flow is simple and side‑effect free beyond updating the screen and calling s.show(). Nothing to change from a correctness standpoint.


49-69: handleEvent cleanly restores the terminal before exiting and handles resize appropriately

Stopping the screen before calling exit(0) ensures the terminal gets restored even though exit bypasses D destructors, which is appropriate for a small demo. The resize branch (resizedisplayHelloWorldsync) also looks correct and matches the intended behavior with the refactored Screen API.


72-85: Main loop’s switch to waitEvent() matches the new single-threaded model

Using:

ts.start();
displayHelloWorld(ts);
for (;;)
{
    Event ev = ts.waitEvent();
    handleEvent(ts, ev);
}

fits the new waitEvent-based API and removes the dependency on threads/channel-style receive. With the default 100ms timeout, EventType.none wakeups are ignored by handleEvent, so the loop behaves effectively as a blocking event loop for this demo. This looks good.

 #8)

This now uses a new api for obtaining events, which allows the application
to run in a single event loop without any locks or threading.  Once small
consequence of this is that if the application wants to work in an MT way
with the screen, then it will need to provide its own guards.

This design means that this can now be used in designs that may not be conducive
to using runtime threads (such as reactor designs found in Weka's mecca library),
and it also should make debugging much simpler.
This way if we stop and restart, we get fresh state each time.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
source/dcell/termio.d (1)

330-375: Critical bugs in poll() implementation.

Two critical issues remain from the previous review:

  1. Line 337: pfd[1].events = 0; should be pfd[1].revents = 0; - currently this overwrites the events mask, preventing signal pipe notifications.

  2. Line 351: poll(pfd.ptr, 1, dly) should be poll(pfd.ptr, 2, dly) - currently only polling the tty fd, completely ignoring the signal pipe.

These bugs prevent the signal pipe from waking up blocked reads on resize events.

Apply this diff:

                 pfd[1].fd = sigRfd;
                 pfd[1].events = POLLRDNORM;
-                pfd[1].events = 0;
+                pfd[1].revents = 0;

                 int dly;
                 if (dur.isNegative || dur == dur.max)
                 {
                     dly = -1;
                 }
                 else
                 {
                     dly = cast(int)(dur.total!"msecs");
                 }

                 string result;

-                long rv = poll(pfd.ptr, 1, dly);
+                long rv = poll(pfd.ptr, 2, dly);
🧹 Nitpick comments (1)
demos/hello/source/hello.d (1)

74-74: Remove redundant import.

std.stdio is already imported at module level (line 13).

 void main()
 {
-    import std.stdio;
-
     auto ts = newScreen();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 899da76 and f97fc23.

📒 Files selected for processing (9)
  • demos/colors/source/colors.d (1 hunks)
  • demos/hello/source/hello.d (2 hunks)
  • demos/mouse/source/mouse.d (2 hunks)
  • source/dcell/evqueue.d (0 hunks)
  • source/dcell/parser.d (1 hunks)
  • source/dcell/screen.d (2 hunks)
  • source/dcell/termio.d (11 hunks)
  • source/dcell/ttyscreen.d (5 hunks)
  • source/dcell/turnstile.d (0 hunks)
💤 Files with no reviewable changes (2)
  • source/dcell/turnstile.d
  • source/dcell/evqueue.d
🚧 Files skipped from review as they are similar to previous changes (2)
  • demos/mouse/source/mouse.d
  • demos/colors/source/colors.d
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: gdamore
Repo: gdamore/dcell PR: 27
File: source/dcell/ttyscreen.d:763-766
Timestamp: 2025-12-08T19:10:45.974Z
Learning: In source/dcell/ttyscreen.d, the newTtyScreen() factory function intentionally does not accept a term parameter because the TtyScreen constructor handles environment variable resolution internally with an optional term parameter. Users needing to override the terminal type should call the constructor directly: new TtyScreen(newDevTty(), "custom-term").
📚 Learning: 2025-12-08T19:10:45.974Z
Learnt from: gdamore
Repo: gdamore/dcell PR: 27
File: source/dcell/ttyscreen.d:763-766
Timestamp: 2025-12-08T19:10:45.974Z
Learning: In source/dcell/ttyscreen.d, the newTtyScreen() factory function intentionally does not accept a term parameter because the TtyScreen constructor handles environment variable resolution internally with an optional term parameter. Users needing to override the terminal type should call the constructor directly: new TtyScreen(newDevTty(), "custom-term").

Applied to files:

  • source/dcell/ttyscreen.d
  • demos/hello/source/hello.d
  • source/dcell/termio.d
🔇 Additional comments (18)
demos/hello/source/hello.d (4)

18-27: LGTM!

The added comment about ASCII handling is helpful documentation. The implementation is appropriate for a demo.


29-47: LGTM!

Formatting improvements with no functional changes.


49-70: LGTM!

The event handling logic is straightforward. Calling ts.stop() before exit(0) ensures proper terminal cleanup.


79-85: Nice demonstration of the new thread-free API.

The new event loop pattern using ts.start() and ts.waitEvent() is clean and clearly demonstrates the refactored API from the PR objectives.

source/dcell/screen.d (2)

96-100: LGTM!

The new waitEvent API clearly documents the polling behavior and the default 100ms timeout is appropriate for event-loop integration.


203-215: LGTM!

Documentation correctly reflects the refactored API that no longer uses threading or std.concurrency.

source/dcell/termio.d (6)

260-319: LGTM!

The select()-based implementation correctly includes sigRfd in the fd set and max calculation, and properly clears the signal pipe. The previous bug has been fixed.


19-34: LGTM!

The version blocks correctly route Apple platforms (OSX, iOS, tvOS, VisionOS) to the select-based implementation, which is appropriate given poll() limitations on /dev/tty for these platforms.


77-77: LGTM!

The addition of a Duration parameter to the read() method enables timeout-based I/O, which is essential for the polling-based event loop design.


384-384: LGTM!

The comment clarifying edge-triggered semantics is helpful documentation.


460-479: LGTM!

The pipe-based signaling mechanism is correctly initialized with non-blocking flags on both ends. The approach of using a pipe to wake blocked readers from the signal handler is a standard and safe pattern.


240-241: LGTM!

The change from Darwin to Apple is correct and consistent with the broader platform coverage defined in the version blocks above.

source/dcell/parser.d (1)

320-320: LGTM!

The comment grammar has been corrected to "fully parsed" as suggested in the previous review.

source/dcell/ttyscreen.d (5)

19-19: LGTM!

The import change from std.concurrency to std.datetime aligns with the removal of thread-based event handling.


472-473: LGTM!

The addition of internal events buffer and parser fields supports the new polling-based event handling model.


227-246: LGTM!

The refactored start() method correctly initializes a new Parser and sets up the terminal without spawning threads. Creating a new Parser on restart properly discards stale parsing state.


393-449: Verify overflow safety with large Duration values.

The waitEvent implementation is well-structured with adaptive polling and residual tracking for partial escape sequences. However, there's a potential edge case at line 400:

expire = MonoTime.currTime() + dur;

If dur is close to Duration.max (but not exactly equal), adding it to the current time could overflow. While line 340 in termio.d checks for dur == dur.max, it may not catch values that are very large but not exactly max.

Consider adding an additional safeguard:

         if (!dur.isNegative)
         {
-            expire = MonoTime.currTime() + dur;
+            if (dur >= (MonoTime.max - MonoTime.currTime()))
+            {
+                expire = MonoTime.max;
+            }
+            else
+            {
+                expire = MonoTime.currTime() + dur;
+            }
         }

Alternatively, verify that the existing checks at line 340 in termio.d (dur == dur.max) are sufficient for all realistic use cases.


409-447: LGTM!

The residual tracking and adaptive polling logic is well-designed. Using a 1ms interval when waiting for partial escape sequences ensures timely timeout detection (e.g., lone ESC key) while using longer intervals during normal operation to reduce CPU usage.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
source/dcell/termio.d (2)

73-77: Update TtyImpl.read docstring for the new dur timeout semantics

The interface comment for read still only talks about blocking and non-blocking modes and doesn’t describe what dur does (zero vs positive vs negative / dur.max) or under which conditions an empty string is returned (timeout vs “no data” vs EINTR).

It would help downstream implementers and callers if the doc clarified the contract, e.g., roughly:

  • dur == Duration.zero: poll and return immediately.
  • dur > 0: block up to dur, possibly returning empty on timeout.
  • dur.isNegative or dur == dur.max: block until input or interrupt.

421-432: Initialize the wakeup byte in handleSigWinCh before writing to sigWfd

handleSigWinCh allocates ubyte[1] buf; but never initializes it before calling unistd.write(sigWfd, buf.ptr, 1);. The actual value is irrelevant for wakeups, but this still writes uninitialized stack contents, which can trip analyzers and is easy to avoid.

Initialize the byte before writing:

-        ubyte[1] buf;
-        import unistd = core.sys.posix.unistd;
-
-        // we do not care if this fails
-        unistd.write(sigWfd, buf.ptr, 1);
+        ubyte[1] buf;
+        import unistd = core.sys.posix.unistd;
+
+        // we do not care if this fails
+        buf[0] = 1;
+        unistd.write(sigWfd, buf.ptr, 1);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06008c8 and 0390be2.

📒 Files selected for processing (1)
  • source/dcell/termio.d (11 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: gdamore
Repo: gdamore/dcell PR: 27
File: source/dcell/ttyscreen.d:763-766
Timestamp: 2025-12-08T19:10:45.974Z
Learning: In source/dcell/ttyscreen.d, the newTtyScreen() factory function intentionally does not accept a term parameter because the TtyScreen constructor handles environment variable resolution internally with an optional term parameter. Users needing to override the terminal type should call the constructor directly: new TtyScreen(newDevTty(), "custom-term").
📚 Learning: 2025-12-08T19:10:45.974Z
Learnt from: gdamore
Repo: gdamore/dcell PR: 27
File: source/dcell/ttyscreen.d:763-766
Timestamp: 2025-12-08T19:10:45.974Z
Learning: In source/dcell/ttyscreen.d, the newTtyScreen() factory function intentionally does not accept a term parameter because the TtyScreen constructor handles environment variable resolution internally with an optional term parameter. Users needing to override the terminal type should call the constructor directly: new TtyScreen(newDevTty(), "custom-term").

Applied to files:

  • source/dcell/termio.d

Comment on lines +260 to +347
version (UseSelect)
{
// this has to use the underlying read system call
import unistd = core.sys.posix.unistd;

ubyte[] buf = new ubyte[128];
auto rv = unistd.read(fd, cast(void*) buf.ptr, buf.length);
if (rv < 0)
return "";
return cast(string) buf[0 .. rv];
// On macOS, we have to use a select() based implementation because poll()
// does not work reasonably on /dev/tty. (This was very astonishing when first
// we discovered it -- POLLNVAL for device files.)
string read(Duration dur = Duration.zero)
{
// this has to use the underlying read system call
import unistd = core.sys.posix.unistd;
import core.sys.posix.sys.select; // Or similar module for select bindings

fd_set readFds;
timeval timeout;
timeval* tvp;

FD_ZERO(&readFds);
FD_SET(fd, &readFds);
FD_SET(sigRfd, &readFds);

if (dur.isNegative)
{
tvp = null;
}
else
{
auto usecs = dur.total!"usecs";

timeout.tv_sec = cast(typeof(timeout.tv_sec)) usecs / 1_000_000;
timeout.tv_usec = cast(typeof(timeout.tv_usec)) usecs % 1_000_000;
tvp = &timeout;
}

import std.algorithm : max;

int num = select(max(fd, sigRfd) + 1, &readFds, null, null, tvp);

if (num < 1)
{
return "";
}

string result;

if (FD_ISSET(fd, &readFds))
{
ubyte[128] buf;
auto nread = unistd.read(fd, cast(void*) buf.ptr, buf.length);
if (nread > 0)
{
result = cast(string)(buf[0 .. nread]).dup;
}
}
if (FD_ISSET(sigRfd, &readFds))
{
ubyte[1] buf;
// this can fail, we're just clearning the signaled state
unistd.read(sigRfd, buf.ptr, 1);
}
return result;
}
}
else
{
string read(Duration dur = Duration.zero)
{
// this has to use the underlying read system call
import unistd = core.sys.posix.unistd;
import core.sys.posix.poll;
import core.sys.posix.fcntl;

pollfd[2] pfd;
pfd[0].fd = fd;
pfd[0].events = POLLRDNORM;
pfd[0].revents = 0;

pfd[1].fd = sigRfd;
pfd[1].events = POLLRDNORM;
pfd[1].revents = 0;

int dly;
if (dur.isNegative || dur == dur.max)
{
dly = -1;
}
else
{
dly = cast(int)(dur.total!"msecs");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

wc -l source/dcell/termio.d

Repository: gdamore/dcell

Length of output: 82


🏁 Script executed:

sed -n '250,360p' source/dcell/termio.d

Repository: gdamore/dcell

Length of output: 3681


🏁 Script executed:

sed -n '320,390p' source/dcell/termio.d | cat -n

Repository: gdamore/dcell

Length of output: 2522


Align Duration semantics between select- and poll-based read implementations

In the version (UseSelect) path, read treats only dur.isNegative as "block forever", but in the poll-based path you treat both dur.isNegative and dur == dur.max as infinite (dly = -1). This creates inconsistent behavior across platforms: read(Duration.max) blocks indefinitely on Apple systems but attempts a conversion on other platforms, risking overflow when Duration.max is converted to microseconds and cast to the timeval fields.

Align the select implementation with the poll implementation by handling Duration.max:

-                if (dur.isNegative)
-                {
-                    tvp = null;
-                }
-                else
-                {
-                    auto usecs = dur.total!"usecs";
-
-                    timeout.tv_sec = cast(typeof(timeout.tv_sec)) usecs / 1_000_000;
-                    timeout.tv_usec = cast(typeof(timeout.tv_usec)) usecs % 1_000_000;
-                    tvp = &timeout;
-                }
+                if (dur.isNegative || dur == dur.max)
+                {
+                    tvp = null;
+                }
+                else
+                {
+                    auto usecs = dur.total!"usecs";
+
+                    timeout.tv_sec  = cast(typeof(timeout.tv_sec)) (usecs / 1_000_000);
+                    timeout.tv_usec = cast(typeof(timeout.tv_usec)) (usecs % 1_000_000);
+                    tvp = &timeout;
+                }
🤖 Prompt for AI Agents
In source/dcell/termio.d around lines 260 to 347, the select-based read treats
only dur.isNegative as “block forever” while the poll-based read treats both
dur.isNegative and dur == dur.max as infinite; update the select branch to also
consider dur == dur.max as infinite (i.e., set tvp = null when dur.isNegative ||
dur == dur.max) and only compute usecs/timeval when the duration is finite, so
both implementations have consistent semantics and avoid overflow when
Duration.max is passed.

@gdamore gdamore merged commit 4abdfd2 into main Dec 10, 2025
3 checks passed
@gdamore gdamore deleted the gdamore/nothread branch December 10, 2025 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants