Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
  • 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)

  • Nov 03 12:12
    raphael-proust closed #971
  • Nov 03 10:26
    raphael-proust commented #971
Anton Bachin
@aantron
@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
@aantron
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
@aantron
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
@aantron
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
@theindigamer

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
@aantron
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
@theindigamer
By worker threads, do you mean OS threads here?
Anton Bachin
@aantron
yes
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
@theindigamer
:+1: thanks :smile:. I think I'm good for now, but I'll have a look at the tutorial soon-ish.
Anton Bachin
@aantron
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
it's typical to write open Lwt.Infix or let open Lwt.Infix in ...
Varun Gandhi
@theindigamer
:+1:
matrixbot
@matrixbot
Orbifx Anton what CPU was your test for the performance of the variants ran an on?
Anton Bachin
@aantron
Orbifx: core i7-2720QM sandy bridge, 2.2 GHz
matrixbot
@matrixbot
Orbifx a beasty one :P
Anton Bachin
@aantron
:)
matrixbot
@matrixbot
Orbifx Are you on Mastodon btw?
Anton Bachin
@aantron
nope. never heard of it. should i be?
Anton Bachin
@aantron
Orbifx: now i am :)