Is there a specific instance where you were writing some code and thought
mutexes would help, or is there a specific project with a problem you'd
like to solve with mutexes? Or IVars and LVars?

I started down this road because I noticed that the test result counting
<https://github.com/racket/rackunit/blob/master/testing-util-lib/rackunit/log.rkt>
in rackunit seems like it might experience data races if test cases in the
same namespace are run from different threads. So I tried to rewrite it
with a semaphore-based lock and found it frustrating, and I got it wrong to
boot.

On Sat, Jan 18, 2020 at 3:06 AM Alexis King <[email protected]> wrote:

> Oh: something more ambitious that I would enjoy having would be an
> implementation of IVars and LVars to avoid needing to think about locking
> entirely.
>
> On Jan 18, 2020, at 05:00, Alexis King <[email protected]> wrote:
>
> 
> I would use mutexes in relatively standard ways, I think, to protect
> critical sections that access shared mutable state or external resources
> that may require some form of serialization. The usual approach of using a
> semaphore works fine, but it does require the aforementioned break
> manipulation song and dance to be entirely robust, and it would be nice to
> not have to worry about it.
>
> On Jan 18, 2020, at 04:46, Jack Firth <[email protected]> wrote:
>
> 
> I appreciate the sentiment about prior art, but I'm already familiar with
> both of those links and a significant part of my day job involves working
> on concurrency frameworks. Specific use cases are more what I'm after. For
> instance, what would you like to use mutexes for?
>
> On Sat, Jan 18, 2020 at 2:34 AM Alexis King <[email protected]> wrote:
>
>> Oh, an addendum: I would be remiss not to mention the excellent paper on
>> the design of Haskell’s asynchronous exception system, which provides both
>> examples of problems in the wild and more general elaboration on both the
>> design space and the particular point within it the authors chose for
>> Haskell. The paper is “Asynchronous Exceptions in Haskell” by Marlow,
>> Peyton Jones, Moran, and Reppy, and it is available here:
>>
>>
>> https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/asynch-exns.pdf
>>
>> Another thing worth reading is this recent blog post by Simon Marlow (the
>> first author of the aforementioned paper) on asynchronous exceptions:
>>
>> https://simonmar.github.io/posts/2017-01-24-asynchronous-exceptions.html
>>
>> On Jan 18, 2020, at 04:27, Alexis King <[email protected]> wrote:
>>
>> I don’t personally have any problems with Racket’s semaphore interface as
>> it exists today. I think having the choice of whether or not to enable
>> breaks mostly makes sense as something the ambient environment controls,
>> not individual pieces of synchronization logic, since you usually want
>> control structures like `with-handlers` and `dynamic-wind` to be the things
>> that mask interrupts in the appropriate places. A hypothetical
>> `with-critical-section` form would be similar in that respect. This allows
>> a limited form of composability between concurrency constructs that is
>> otherwise hard to achieve.
>>
>> For the reasons I’ve already given, I think it would be more useful to
>> offer higher-level concurrency primitives like events, mutexes, etc., since
>> those could offer more structure based on the particular use case in
>> question. (Also, I realized Haskell’s MVars are basically just Racket
>> channels, though Racket’s channels don’t have a peek operation.)
>>
>> More generally, I think Haskell’s concurrency libraries are good prior
>> art here that would be worth looking at. Haskell’s “asynchronous
>> exceptions” are directly analogous to Racket’s breaks, though Haskell
>> allows arbitrary exceptions to be raised asynchronously rather than only
>> allowing the more restrictive interface of `thread-break`. Haskell’s `mask`
>> operator correspond’s to Racket’s `parameterize-break`. Even though the
>> primitives are essentially the same, Haskell’s libraries provide a much
>> richer set of higher-level abstractions, both in the standard library (see
>> Control.Exception and Control.Concurrent.*) and in other packages.
>>
>> On Jan 18, 2020, at 04:04, Jack Firth <[email protected]> wrote:
>>
>> I am making a new concurrency abstraction, and I already have to work
>> around the interface because it forces me to make this choice at every use
>> site. What I was planning on doing was pushing this decision into the value
>> itself, rather than the use site. So what if `make-semaphore` had a
>> `#:break-handling-mode` argument that controlled whether or not waiting on
>> that particular semaphore would either enable breaks, or check that breaks
>> or disabled, or neither of those?
>>
>> On Sat, Jan 18, 2020 at 1:45 AM Alexis King <[email protected]>
>> wrote:
>>
>>> No, I don’t think so, and here’s why: imagine a library provides an
>>> abstraction that internally uses semaphores as events. The library uses
>>> `semaphore-wait` to wait on the event. The client of this library now has
>>> the option to disable breaks if it turns out this code is actually going to
>>> be used *inside* a larger critical section, and they don’t want breaks
>>> to be re-enabled by the library! They really want everything in the
>>> critical section to keep breaks disabled. So in that case, the
>>> break-agnostic behavior of `semaphore-wait` really is the right one.
>>>
>>> This is what I mean by semaphore’s being a low-level primitive, though.
>>> There are lots of different behaviors one might want that could be better
>>> served by higher-level abstractions that can make more assumptions about
>>> how they’ll be used, but semaphores have to support all of them. I think it
>>> makes sense that they provide the minimal set of behaviors needed to
>>> implement those things—it keeps the building blocks as simple and modular
>>> as possible. You can always implement the more complex behavior on top, but
>>> it’d be annoying to discover you needed to work around the interface trying
>>> to protect you from yourself while you’re implementing a new concurrency
>>> abstraction.
>>>
>>> On Jan 18, 2020, at 03:36, Jack Firth <[email protected]> wrote:
>>>
>>> Wouldn't you want to *force* the first thread to wait with
>>> semaphore-wait/enable-break in that case? Since if they're disabled then
>>> that thread can't be cooperatively terminated. If you use `semaphore-wait`
>>> it seems like you completely hand off control over whether breaks are
>>> enabled or not, which seems like something that use sites should care about
>>> one way or the other. What sort of semaphore-based communication would be
>>> truly indifferent to whether breaking is enabled?
>>>
>>> On Sat, Jan 18, 2020 at 1:28 AM Alexis King <[email protected]>
>>> wrote:
>>>
>>>> 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]>
>>>> 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/CAAXAoJXNz%2BNLkQBehixf2C%3DwPNhq9qYEYtYog_2uU%2BdhHZYorg%40mail.gmail.com.

Reply via email to