| ... | @@ -201,8 +201,22 @@ const Closure = struct { | ... | @@ -201,8 +201,22 @@ const Closure = struct { |
| 201 | // We can send a signal to interrupt the syscall, but if it arrives before | 201 | // We can send a signal to interrupt the syscall, but if it arrives before |
| 202 | // the syscall instruction, it will be missed. Therefore, this code tries | 202 | // the syscall instruction, it will be missed. Therefore, this code tries |
| 203 | // again until the cancellation request is acknowledged. | 203 | // again until the cancellation request is acknowledged. |
| 204 | const max_attempts = 3; | 204 | |
| 205 | for (0..max_attempts) |_| { | 205 | // 1 << 10 ns is about 1 microsecond, approximately syscall overhead. |
| | 206 | // 1 << 20 ns is about 1 millisecond. |
| | 207 | // 1 << 30 ns is about 1 second. |
| | 208 | // |
| | 209 | // On a heavily loaded Linux 6.17.5, I observed a maximum of 20 |
| | 210 | // attempts not acknowledged before the timeout (including exponential |
| | 211 | // backoff) was sufficient, despite the heavy load. |
| | 212 | // |
| | 213 | // The time wasted here sleeping is mitigated by the fact that, later |
| | 214 | // on, the system will likely wait for the canceled task, causing it |
| | 215 | // to indefinitely yield until the canceled task finishes, and the |
| | 216 | // task must acknowledge the cancel before it proceeds to that point. |
| | 217 | const max_attempts = 22; |
| | 218 | |
| | 219 | for (0..max_attempts) |attempt_index| { |
| 206 | if (std.Thread.use_pthreads) { | 220 | if (std.Thread.use_pthreads) { |
| 207 | const rc = std.c.pthread_kill(signal_id, .IO); | 221 | const rc = std.c.pthread_kill(signal_id, .IO); |
| 208 | if (is_debug) assert(rc == 0); | 222 | if (is_debug) assert(rc == 0); |
| ... | @@ -219,11 +233,14 @@ const Closure = struct { | ... | @@ -219,11 +233,14 @@ const Closure = struct { |
| 219 | return; | 233 | return; |
| 220 | } | 234 | } |
| 221 | | 235 | |
| 222 | // TODO make this a nanosleep with 1 << attempt duration | 236 | var timespec: posix.timespec = .{ |
| 223 | std.Thread.yield() catch {}; | 237 | .sec = 0, |
| | 238 | .nsec = @as(isize, 1) << @intCast(attempt_index), |
| | 239 | }; |
| | 240 | _ = posix.system.nanosleep(&timespec, &timespec); |
| 224 | | 241 | |
| 225 | switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { | 242 | switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { |
| 226 | .requested => continue, | 243 | .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall. |
| 227 | .none, .acknowledged => return, | 244 | .none, .acknowledged => return, |
| 228 | .signal_id => |new_signal_id| signal_id = new_signal_id, | 245 | .signal_id => |new_signal_id| signal_id = new_signal_id, |
| 229 | } | 246 | } |