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 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/BC6EA48A-0EDE-4AF0-B7F9-897A3F677987%40gmail.com.

Reply via email to