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]> 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]> 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]> 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]> 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]>
>> 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]> 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/CAAXAoJW1cfikQ7bOPbqPRL0zUUKvkF7Nh9pGM2DXYOObSSHqsQ%40mail.gmail.com.

Reply via email to