trhawes
2019-8-12 11:11:59

I am trying to write some code that will extract the contents of the provide directive from a racket file. I’ve started down the path of just trying to convert the code into one giant sexpr. I know racket has stellar language parsers, but I am really just after one piece of information (I’ll be traversing a directory tree reading racket files for this info). I know I can just convert the file into a string and find what I am looking for in a regex-match, but was thinking the conversion of code into data would be a better approach. Any suggestions?


soegaard2
2019-8-12 11:18:17

@trhawes You can expand the module. During expansion the expander records information on what is provided. See https://docs.racket-lang.org/reference/Expanding_Top-Level_Forms.html?q=provide#%28part._modinfo%29


stefan.kruger
2019-8-12 13:46:06

Trying to figure out how to use Racket’s synchronization primitives, async channels and threads. If two threads sends data back and forth over a (unbounded, async) channel, how can I detect deadlock - thread 0 is blocking on read and thread 1 is blocking on read. Do I need to wrap the channel in a semaphore somehow?


stefan.kruger
2019-8-12 13:46:37

It feels like there is an elegant solution here that’s eluding me.


lexi.lambda
2019-8-12 14:45:38

@stefan.kruger I think the answer is usually “don’t do that.” Why do you need two threads to coordinate that way? If it’s because they share state, then usually you’d want to use some sort of lock-protected mutable state. If it’s because they need to send messages to one another, then usually you’d use async channels or thread mailboxes (which are themselves basically async channels).

If you want backpressure in that kind of system, you still probably want unidirectional data flow rather than having threads that are both reading and writing to the same channel. If there’s a 1-to–1 mapping from input messages to output messages, what about using two async channels, each with a buffer size of 1? One of them is a channel from thread A to thread B, and the other is a channel from B to A. That way, you can’t ever have deadlock as long as each thread doesn’t commit a protocol violation (which is a much bigger sin than just happening to both read at the same time).


lexi.lambda
2019-8-12 14:47:25

Unless what you’re saying is not that you’re trying to figure out how to write a deadlock-free algorithm but how to detect deadlock for error-reporting purposes (where the “recovery” from the deadlock is just to panic).


stefan.kruger
2019-8-12 14:51:22

What I’m trying to do is a (very) simple simulation of two processes where both can consume data from and produce data for the other — one of the Advent of Code exercises. It’s easily done without threads (as I did in my python solution: https://github.com/xpqz/aoc-17/blob/master/day18p2.py) but thought it might be instructive to try the asynch channel approach for learning.


stefan.kruger
2019-8-12 14:52:18

So process A needs to know if to block, unless process B is already waiting for process A, in which case we;re done.


stefan.kruger
2019-8-12 14:54:51

lexi.lambda
2019-8-12 14:56:33

Could you tell me what the problem is? I don’t think I can actually access part 2 without solving part 1.


stefan.kruger
2019-8-12 14:57:23

stefan.kruger
2019-8-12 14:57:51

I made part 1 like so:


stefan.kruger
2019-8-12 14:58:08

lexi.lambda
2019-8-12 14:58:50

Yes, I mean I don’t think the advent of code website shows the problem statement for part 2 until you have solved part 1, and I have not solved part 1.


stefan.kruger
2019-8-12 14:59:10

Ok, one second.


stefan.kruger
2019-8-12 14:59:46

lexi.lambda
2019-8-12 15:01:47

I see, so the machines deadlocking is fundamentally part of the problem. In that case, I don’t think Racket provides any built-in way to ask if two threads are both deadlocked on one another.


lexi.lambda
2019-8-12 15:03:32

I do believe Racket actually does detect many kinds of deadlocks, since IIRC, a thread will be garbage collected if the runtime determines it is blocked on something that will never unblock. But that’s a best-effort sort of thing, so there aren’t any guarantees that the deadlock will be detected or that the thread will be GC’d in any reasonable timeframe.


stefan.kruger
2019-8-12 15:06:43

Can I set a time-out on a an async channel read?


lexi.lambda
2019-8-12 15:07:13

Yes, you can do that, but the trouble with that kind of approach is that it’s fundamentally a race condition.


stefan.kruger
2019-8-12 15:07:15

Or otherwise back to some kind of asynch-channel-get-try + some global state?


stefan.kruger
2019-8-12 15:07:56

Or a separate out-of-band channel that says other is blocked?


stefan.kruger
2019-8-12 15:09:25

Clojure approach: https://gist.github.com/lnostdal/3409086d436359888ade175311fe0a55 — although I admittedly don’t understand that fully.


lexi.lambda
2019-8-12 15:12:16

Yes, you could definitely do that. You could have some sort of intermediary thread that is actually managing the flow of values between the two other threads, and you could have a protocol where a read request involves sending a special “read” message to an outbound channel followed by reading from an inbound channel, while a send request involves sending some other message to the outbound channel. The manager thread could coordinate the two threads by doing some bookkeeping that tracks if both threads have sent more read requests than write requests and declaring them deadlocked if that happens. But I’m not sure that’s especially elegant.


stefan.kruger
2019-8-12 15:13:39

Should have gone for the obvious solution, clearly :slightly_smiling_face:


soegaard2
2019-8-12 15:14:01

@stefan.kruger Do you happen to know “The little book of semaphores”?



soegaard2
2019-8-12 15:14:17

It contains solutions for common scenarios.


soegaard2
2019-8-12 15:15:05

I am not sure, but section “4.3 No-starve mutex” sounds a little like your problem.


stefan.kruger
2019-8-12 15:15:42

Wow that looks like a book I could have done with at university…


lexi.lambda
2019-8-12 15:17:24

@soegaard2 The trouble here is that the problem isn’t about preventing deadlocks or starvation—the problem statement explicitly requires allowing deadlocks—the problem is detecting when a deadlock has occurred.


soegaard2
2019-8-12 15:20:06

Sorry - skipped the actual problem statement. A manager thread seems a good idea.


lexi.lambda
2019-8-12 15:20:48

It’s possible that there’s a solution without a manager thread using locks or events that I’m overlooking, but I haven’t been able to come up with one given the primitive Racket provides.


soegaard2
2019-8-12 15:21:29

Is it enough for thread A and B to send messages “reading”, “done reading” “writing” “done writing” each time they use the channel connecting A and B? If both threads read at the same time, the manager knows there is a deadlock.


lexi.lambda
2019-8-12 15:22:16

No—it’s especially tricky due to the fact that the processes are communicating via buffered channels, so detecting that both threads are reading simultaneously isn’t enough. They need to both be reading simultaneously and have nothing left to read. Otherwise there’s a race condition where both threads might be reading at the same time, but they are not deadlocked.


stefan.kruger
2019-8-12 15:29:36

So I think what’s needed is (other-process-blocked? my-pid) to be checked when (async-channel-get-try ...) returns #f.


stefan.kruger
2019-8-12 15:30:46

I know that the other process when blocked cannot unblock unless I put a value on the channel, so when I’m in the read bit there should be no race.


trhawes
2019-8-13 00:11:59

It is not very clear from the linked docs how one might expand a racket file. I am going to load something close to 70 racket files in one pass, is this going to be the most efficient way?


sydney.lambda
2019-8-13 03:28:58

Is this supposed to fail with megaparsack?: (parse-string (string-ci/p "LDA") "lda") => (failure (message (srcloc 'string 1 0 1 2) #\d '("DA"))) I had a number of things failing and I just couldn’t figure out why. They all turned out to be due to this, as they work if the strings are of the same case. Am I misunderstanding the way string-ci/p (or string-ci=? in general) works? (string-ci=? "LDA" "lda") works just fine.


sydney.lambda
2019-8-13 03:38:06

I’m likely way off, but the code for string-ci/p calls chars/p: (define (string-ci/p str) (chars/p str char-ci/p)) which looks like: (define (chars/p str char-parser) (if (zero? (string-length str)) (pure "") (label/p str (do (char-parser (string-ref str 0)) (string/p (substring str 1)) (pure str))))) so, string-ci/p only seems to test the first character case-insensitively (using char-parser), but then defaults to string/p to recur on the rest of the string. I think?

Apologies if this is just a silly error on my part.


notjack
2019-8-13 03:47:57

@sydney.lambda That looks like a bug in megaparsack. I suggest opening an issue https://github.com/lexi-lambda/megaparsack


sydney.lambda
2019-8-13 03:50:34

Thanks @notjack. I always err on the side of “I’m doing something wrong” rather than “there’s a bug in the library”, so I thought I’d better ask first.