Actually, I change my mind, I can trivially think of a case where it’s fine: if 
you’re just using a semaphore as an event. One thread waits with 
`semaphore-wait`, another thread calls `semaphore-post`, and after the count is 
decremented, it’s never re-incremented. It’s just used to gate execution, not 
guard access to a resource. No need to disable breaks here.

(Also, an aside: I think your `car`/`cdr` example is different, because 
`car`/`cdr`’s checks on pairs guard against memory corruption in the Racket 
runtime, and Racket is a memory-safe language. A better comparison would be 
that `car`/`cdr` don’t check whether or not their argument is a proper list—the 
higher-level `first`/`rest` do that, instead.)

> On Jan 18, 2020, at 03:21, Jack Firth <[email protected]> wrote:
> 
> It isn't clear to me either. I can't think of a use case for it, but I'm 
> hoping either somebody else can or somebody can confirm that it's not a good 
> API precedent. I'm trying to build some concurrency libraries 
> <https://github.com/jackfirth/rebellion/issues/397> and I'd like to be sure 
> there isn't some important use case I'm missing.
> 
> On Sat, Jan 18, 2020 at 1:14 AM Alexis King <[email protected] 
> <mailto:[email protected]>> wrote:
> Like I said, it isn’t clear to me that all uses of `semaphore-wait` when 
> breaks are enabled are incorrect. You could argue that then you should have a 
> `semaphore-wait/trust-me-even-though-breaks-are-enabled`, and sure, I don’t 
> think that would necessarily be bad. I just imagine the API just wasn’t 
> originally designed that way for some reason or another, possibly simply 
> because it wasn’t considered at the time. Maybe Matthew can give a more 
> satisfying answer, but I don’t know; I’m just speculating.
> 
>> On Jan 18, 2020, at 03:10, Jack Firth <[email protected] 
>> <mailto:[email protected]>> wrote:
>> 
>> I don't see how it has to do with semaphores being low-level. If waiting on 
>> a semaphore while breaks are enabled is almost certainly wrong, checking 
>> whether breaks are enabled and raising an error seems like a way more 
>> sensible default behavior than just silently doing something that's almost 
>> certainly wrong. If car and cdr can check their arguments by default, 
>> shouldn't semaphores guard against misuse too?
>> 
>> On Sat, Jan 18, 2020 at 1:04 AM Alexis King <[email protected] 
>> <mailto:[email protected]>> wrote:
>> It is guaranteed to leave the semaphore in a consistent state, from the 
>> perspective of the implementation of semaphores. No matter what you do, you 
>> won’t ever corrupt a semaphore (assuming you’re not using unsafe operations 
>> and assuming the runtime is not buggy).
>> 
>> But perhaps you mean inconsistent from the point of view of the application, 
>> not from the point of view of the Racket runtime. In that case, it’s true 
>> that when using semaphores as locks, using them in a context where breaks 
>> are enabled is almost certainly wrong. It’s not immediately clear to me that 
>> there aren’t any valid uses of semaphores where you would want breaks to be 
>> enabled, but I admit, I have no idea what they are.
>> 
>> Semaphores are low-level primitives, though, so I think it makes some sense 
>> for them to just do the minimal possible thing. Perhaps a library ought to 
>> offer a slightly more specialized “critical section” abstraction a la 
>> Windows (or perhaps something like Haskell’s MVars) that manages disabling 
>> interrupts in the critical section for you. (Why doesn’t this exist already? 
>> My guess is that most Racket programmers don’t worry about these details, 
>> since they don’t call `break-thread` anywhere, and they want SIGINT to just 
>> kill their process, anyway.)
>> 
>>> On Jan 18, 2020, at 02:54, Jack Firth <[email protected] 
>>> <mailto:[email protected]>> wrote:
>>> 
>>> I do understand all of that, and you're right that "kill-safe" isn't what I 
>>> meant.
>>> 
>>> What I'm confused about is why, if it's inherently not guaranteed to leave 
>>> the semaphore in a consistent state, semaphore-wait attempts to work at all 
>>> if breaks are enabled. Why not raise some helpful error like "it's unsafe 
>>> to wait on a semaphore while breaks are enabled, did you forget to disable 
>>> breaks?". What's the actual use case for calling semaphore-wait (and not 
>>> semaphore-wait/enable-break) while breaks are enabled?
>>> 
>>> On Sat, Jan 18, 2020 at 12:47 AM Alexis King <[email protected] 
>>> <mailto:[email protected]>> wrote:
>>> Killing a thread is different from breaking a thread. Killing a thread 
>>> kills the thread unrecoverably, and no cleanup actions are run. This 
>>> usually isn’t what you want, but there’s always a tension between these 
>>> kinds of things: defensive programmers ask “How do I make myself unkillable 
>>> so I can safely clean up?” but then implementors of a dynamic environment 
>>> (like, say, DrRacket) find themselves asking “How do I kill a runaway 
>>> thread?” Assuming you’re not DrRacket, you usually want `break-thread`, not 
>>> `kill-thread`.
>>> 
>>> But perhaps you know that already, and your question is just about 
>>> breaking, so by “kill-safe” you mean “break-safe.” You ask why 
>>> `semaphore-break` doesn’t just disable breaking, but that wouldn’t help 
>>> with the problem the documentation alludes to. The problem is that there’s 
>>> fundamentally a race condition in code like this:
>>> 
>>>     (semaphore-wait sem)
>>>     ; do something important
>>>     (semaphore-post sem)
>>> 
>>> If this code is executed in a context where breaks are enabled, it’s not 
>>> break-safe whether or not `semaphore-wait` were to disable breaks while 
>>> waiting on the semaphore. As soon as `semaphore-wait` returns, the queued 
>>> break would be delivered, the stack would unwind, and the matching 
>>> `semaphore-post` call would never execute, potentially holding a lock 
>>> forever. So the issue isn’t that the semaphore’s internal state gets 
>>> somehow corrupted, but that the state no longer reflects the value you want.
>>> 
>>> The right way to write that code is to disable breaks in the critical 
>>> section:
>>> 
>>>     (parameterize-break #f
>>>       (semaphore-wait sem)
>>>       ; do something important
>>>       (semaphore-post sem))
>>> 
>>> This eliminates the race condition, since a break cannot be delivered until 
>>> the `semaphore-post` executes (and synchronous, non-break exceptions can be 
>>> protected against via `dynamic-wind` or an exception handler). But this 
>>> creates a new problem, since if a break is delivered while the code is 
>>> blocked on the semaphore, it won’t be delivered until the semaphore is 
>>> posted/unlocked, which may be a very long time. You’d really rather just 
>>> break the thread, since it hasn’t entered the critical section yet, anyway.
>>> 
>>> This is what `semaphore-wait/enable-break` is for. You can think of it as a 
>>> version of `semaphore-wait` that re-enables breaks internally, inside its 
>>> implementation, and it installs an exception handler to ensure that if a 
>>> break is delivered at the worst possible moment (after the count has been 
>>> decremented but before breaks are disabled again), it reverses the change 
>>> and re-raises the break exception. (I have no idea if this is how it’s 
>>> actually implemented, but I think it’s an accurate model of its behavior.) 
>>> This does exactly what we want, since it ensures that if we do enter the 
>>> critical section, breaks are disabled until we exit it, but we can still be 
>>> interrupted if we’re blocked waiting to enter it.
>>> 
>>> So it’s not so much that there’s anything really special going on here, but 
>>> more that break safety is inherently anti-modular where state is involved, 
>>> and you can’t implement `semaphore-wait/enable-break`-like constructs if 
>>> you only have access to the `semaphore-wait`-like sibling.
>>> 
>>> > On Jan 17, 2020, at 22:37, Jack Firth <[email protected] 
>>> > <mailto:[email protected]>> wrote:
>>> > 
>>> > The docs for semaphores say this:
>>> > 
>>> > In general, it is impossible using only semaphore-wait to implement the 
>>> > guarantee that either the semaphore is decremented or an exception is 
>>> > raised, but not both. Racket therefore supplies 
>>> > semaphore-wait/enable-break (see Semaphores), which does permit the 
>>> > implementation of such an exclusive guarantee.
>>> > 
>>> > I understand the purpose of semaphore-wait/enable-break, but there's 
>>> > something about semaphore-wait that confuses me: why does it allow 
>>> > breaking at all? My understanding is that if breaks are enabled, 
>>> > semaphore-wait still tries to block and decrement the counter, even 
>>> > though a break at any time could destroy the integrity of the semaphore. 
>>> > Does that mean it's not kill-safe to use a semaphore as a lock? Wouldn't 
>>> > it be safer if semaphore-wait automatically disabled breaks while waiting?
>>> 
>> 
> 

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/racket-users/512575E7-01D1-415D-85D8-BDA1B1F758DF%40gmail.com.

Reply via email to