Where communities thrive

  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
  • Dec 05 22:19
    ivg commented #970
  • Dec 04 22:42
    hansole commented #972
  • Nov 27 08:30
    favonia closed #855
  • Nov 27 08:30
    favonia commented #855
  • Nov 16 13:51
    ChrisVine commented #970
  • Nov 16 13:48
    ChrisVine commented #970
  • Nov 16 11:57
    ChrisVine commented #970
  • Nov 16 11:44
    ChrisVine commented #970
  • Nov 16 11:43
    ChrisVine commented #970
  • Nov 16 11:42
    ChrisVine commented #970
  • Nov 14 20:41
    edwintorok commented #959
  • Nov 14 15:05
    anuragsoni commented #972
  • Nov 14 12:26
    mseri commented #972
  • Nov 14 12:26
    mseri commented #972
  • Nov 14 09:23
    raphael-proust commented #972
  • Nov 14 09:22
    raphael-proust commented #972
  • Nov 13 19:43
    gasche opened #972
  • Nov 07 12:47
    hannesm commented #964
  • Nov 03 12:34
    raphael-proust commented #964
  • Nov 03 12:12

    raphael-proust on master

    Lwt_domain: update to domainsli… Remove workflow that is not use… Merge pull request #971 from Su… (compare)

Varun Gandhi
Thanks for the help once again :smile:
Anton Bachin
Varun Gandhi

@aantron, how does one exit out of an infinite loop?

I thought that the following should work.

let rec f () = f ()
let loop () = Lwt.return (f ())
let timeout =
  let%lwt () = Lwt_unix.sleep 1.0 in
  Lwt.return ()
let _ = Lwt_main.run (Lwt.pick [loop (); timeout])

Does it not work because no allocations are being done?

@theindigamer Other "threads" only run at bind points, you can't interrupt a busy loop
Using something like this as a loop should work: let rec f () = Lwt_main.yield () >>= fun () -> f ()
Anton Bachin
@theindigamer :+1: to what @copy said. basically, an Lwt program is an ordinary OCaml program, including eager evaluation and everything else that is normal in OCaml. Promises are just slightly fancy refs, and Lwt only gets involved when you call into it. if you call f () anywhere, OCaml will evaluate it to completion like in any other single-threaded program. f as you defined it does not interact with Lwt at all. The Lwt.return outside f doesn't do anything special. It just waits for the result of f () (which never evaluates to a result), and wraps it in an already-completed promise once the result is available. It's basically the same as the ref : 'a -> 'a ref function. so, once the program above calls loop (), it's stuck forever. it never calls Lwt.pick or Lwt_main.run, or even that Lwt.return.
the reason timeout allows things to run concurrently, and f () doesnt, is because Lwt_unix.sleep immediately returns a promise that won't be resolved for one second (as opposed to actually sleeping for one second). bind (let%lwt) then immediately attaches a callback to that promise, and returns another promise that wont get resolved until (1) the sleep is done, and then (2) the callback runs, and (3) the promise returned by the callback is resolved
Varun Gandhi

@aantron, now I understand why the previous code didn't work.

However, I'm 100% sure how @copy's code example works and what exactly yield is doing -
the docs say:

yield () is a threads which suspends itself and then resumes as soon as possible and terminates.

Is it acting like an instantaneous "pause" between the consecutive evaluation steps? So the scheduler can stop it?

Also, if I had another function that was computing something -- say like:

let g n = if n = 0 then 0 else g n

then one way to make a "promise version" of this would be:

let rec g' n = Lwt.(Lwt_main.yield () >>= fun () ->
                    if n = 0 then return 0 else g' n)

Would it be correct to say that, one cannot generically "lift" g to g' and one has to write g' separately?

Also, thanks for the clarification on the ref thing -- I had the wrong mental model that lwt basically uses a lightweight version of OS threads, but it essentially behaves in the same manner (you can terminate an OS thread at an arbitrary time, for example). Clearly, that's not the case.
Anton Bachin
@theindigamer yeah, we still have to rewrite the manual to make this as clear as possible, and get rid of all references to "threads"
regarding @copy's code, if you look at Lwt_main.yield, it has type unit -> unit Lwt.t. so what it's doing is creating a promise, in fact an unresolved one. so when you bind on it (>>=, or let%lwt), the code you are attaching gets stuffed into a callback that's attached to that unresolved promise
the yield promise will be resolved by Lwt_main.run the next time it gets a chance to drive the I/O loop
so yes, it's basically a pause that allows some other code to run. it's kind of a Lwt_unix.sleep 0.
there isn't any scheduler starting or stopping any of this
the way lwt works, is everything runs immediately, except that when you call things like Lwt_unix.sleep, they start some background I/O operation, that knows how to tell Lwt_main.run that it finished
(the start runs immediately, bu this notifying of Lwt_main.run will happen later)
did you try following the tutorial in the new lwt.mli btw?
Anton Bachin
the way to understand what happens in Lwt is with promises and callbacks. each promise is either resolved or pending
when you bind on a promise, if it is pending, the callback you added goes into a callback list attached to the promise
when the pending promise gets resolved later, the callback is called
all of this is ordinary ocaml code. there is no "scheduler" like in an OS
so if you create a promise, attach a callback to it using bind, and then resolve the promise (using Lwt.wakeup_later), its just straight line ocaml code to load the callback list from the formerly pending promise, replace its state by resolved, and then run each callback in the list
although those callbacks typically trigger even more promises to be resolved, so this can happen in a big loop, the computation will look like a tree
but the loop is not provided by lwt - its just Lwt.wakeup_later traversing all these lists that are stored with pending promises
this is enough for lwt to be "concurrent", because if you look at a big web of promises hanging out in memory, its hard to predict which callback will run before which other one :)
(if you resolve one of them)
but thats a stupid kind of concurrency. the interesting part of lwt is that it allows you to run I/O in parallel
Anton Bachin
the way this works is when you start an I/O operation, say read from a file, a helper in lwt creates a promise for you. you can then attach your own callbacks to it
(Lwt_unix.read, for example)
the helper also uses a background system thread or non-blocking I/O to run the real read(2) operation. when that completes, it resolves the promise it gave you, triggering what i just described above
the job of Lwt_main.run is to block the process until the next such I/O promise gets resolved
so basically, in Lwt, you create a bunch of I/O promises (and some that aren't I/O). Lwt always runs all the non-I/O code that creates, or is triggered by promises, to completion. once it runs out of your code to run, Lwt_main.run puts the process to sleep. the real underlying I/O is meanwhile still running, in parallel
once the first such real I/O completes, Lwt_main.run wakes up the process, resolves the associated promise, which triggers another phase of running as much code as possible
Anton Bachin
Lwt_main.yield () btw is just the most trivial I/O promise possible. yield creates a pending promise, then puts a function into the back of Lwt_main.run's queue to resolve it right away. this allows Lwt_main.run to first run any code in callbacks associated with other I/O promises that have resolved recently, then Lwt_main.run resolves the yield () promise, after draining the rest of the queue
(btw dont worry about asking questions, the more i have to explain this, the easier it is to write the properly grammatical version for the manual later :p)
Varun Gandhi

I haven't followed the tutorial, I thought I'll just jump in and pick it up as I go :sweat_smile: , clearly a suboptimal approach. I was too fixated on solving the problem at hand rather than looking at the bigger picture.

I'll go through the tutorial now.

I think I follow most of your explanation. That said,

puts the process to sleep. the real underlying I/O is meanwhile still running, in parallel

has me a bit confused. Are there multiple OS processes here, with one sleeping and another one doing IO?

Anton Bachin
nope, there is one process (for your program)
the I/O is running in parallel because thats how system I/O works :) when you do a normal blocking read, for instance, the kernel puts your process to sleep, while doing the read. so the read is running in parallel (well not, because your process is asleep)
but if you pair read with select, epoll, kqueue, or run it in a worker thread, then the read is running in parallel, because the kernel either notifies you through select, epoll, kqueue when the read completes, or puts to sleep only the worker thread
thats basically exactly how lwt_main.run works on the inside
the I/O parts of lwt are basically a big wrapper around select (or whatever non-blocking I/O API exists on the system) plus worker threads, that (1) runs as much code as possible, and puts the process to sleep when all remaining code is waiting for I/O (2) resolves promises when some I/O completes
Varun Gandhi
By worker threads, do you mean OS threads here?
Anton Bachin
after clarifying the manual, "threads" will always refer to OS threads :) right now the manual misleadingly uses that word for lwt promises as well
Varun Gandhi
:+1: thanks :smile:. I think I'm good for now, but I'll have a look at the tutorial soon-ish.
Anton Bachin
getting an accurate mental model for Lwt is no easy task. it took me some weeks or months of sporadic improvements to get it. its good to go through this process again. hopefully it can be condensed into helpful docs :)
sure :) good luck :)
the lwt.mli tutorial is kind of a draft for exercising just that module, i probably will take it out into a broader main tutorial later, that can be more conceptual
also for your question about inserting Lwt_yield automatically into g, the answer is yes, you have to do it manually. however, ideally, this should be very rare
also you shouldn't open Lwt. the operators are provided by Lwt.Infix