Bounded read of the host child's one-byte readiness signal. A child that * deadlocks before it can write (a fork-time allocator stall under a sanitizer * is the classic cause) must never hang the whole suite on an unbounded read: * poll to a generous deadline, then let the caller's ASSERT_TRUE(ready) fail * cleanly. Returns the byte, or 0 when the child died, closed the pipe, or * never answe
| 1702 | * cleanly. Returns the byte, or 0 when the child died, closed the pipe, or |
| 1703 | * never answered in time. */ |
| 1704 | static char cli_scope_wait_ready(int fd, uint32_t timeout_ms) { |
| 1705 | uint64_t deadline = cbm_now_ms() + timeout_ms; |
| 1706 | for (;;) { |
| 1707 | int64_t remaining = (int64_t)deadline - (int64_t)cbm_now_ms(); |
| 1708 | if (remaining <= 0) { |
| 1709 | return 0; |
| 1710 | } |
| 1711 | struct pollfd pfd = {.fd = fd, .events = POLLIN, .revents = 0}; |
| 1712 | int r = poll(&pfd, 1, (int)remaining); |
| 1713 | if (r < 0) { |
| 1714 | if (errno == EINTR) { |
| 1715 | continue; |
| 1716 | } |
| 1717 | return 0; |
| 1718 | } |
| 1719 | if (r == 0) { |
| 1720 | return 0; /* deadline reached with no signal */ |
| 1721 | } |
| 1722 | char ready = 0; |
| 1723 | ssize_t got = read(fd, &ready, 1); |
| 1724 | if (got == 1) { |
| 1725 | return ready; |
| 1726 | } |
| 1727 | if (got < 0 && errno == EINTR) { |
| 1728 | continue; |
| 1729 | } |
| 1730 | return 0; /* EOF (child gone) or error */ |
| 1731 | } |
| 1732 | } |
| 1733 | |
| 1734 | /* Reap the host child within a bound: the release signal makes a healthy child |
| 1735 | * break within ~50 ms and finish teardown in a few seconds, so a child still |
no test coverage detected