diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..ca79ca5b4d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index e24417b2e7..d0200de666 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -1,4 +1,4 @@ -name: Main workflow +name: Builds, tests & co on: pull_request: @@ -8,21 +8,23 @@ on: - cron: 0 1 * * MON jobs: - build: + build-and-test: strategy: fail-fast: false matrix: os: - ubuntu-latest ocaml-compiler: - - 4.08.x - - 4.09.x - - 4.10.x - - 4.11.x - - 4.12.x - - 4.13.x - - 4.14.x - - 5.0.x + - "4.08" + - "4.09" + - "4.10" + - "4.11" + - "4.12" + - "4.13" + - "4.14" + - "5.0" + - "5.1" + - "5.2" libev: - true - false @@ -33,39 +35,39 @@ jobs: *.opam include: - os: ubuntu-latest - ocaml-compiler: ocaml-variants.4.14.0+options,ocaml-option-flambda,ocaml-option-musl,ocaml-option-static + ocaml-compiler: ocaml-variants.5.2.0+options,ocaml-option-flambda,ocaml-option-musl,ocaml-option-static,ocaml-option-no-compression libev: false ppx: true local-packages: | *.opam - os: macos-latest - ocaml-compiler: 4.14.x + ocaml-compiler: "5.2" libev: true ppx: true local-packages: | *.opam - os: windows-latest - ocaml-compiler: 4.14.x + ocaml-compiler: "5.2" libev: false ppx: true local-packages: | *.opam - os: ubuntu-latest - ocaml-compiler: 4.08.x + ocaml-compiler: "5.2" libev: true ppx: false local-packages: | *.opam !lwt_ppx.opam - os: macos-latest - ocaml-compiler: 4.08.x + ocaml-compiler: "5.2" libev: true ppx: false local-packages: | *.opam !lwt_ppx.opam - os: windows-latest - ocaml-compiler: 4.08.x + ocaml-compiler: "5.2" libev: false ppx: false local-packages: | @@ -75,38 +77,47 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Checkout code - uses: actions/checkout@v2 + - name: Checkout tree + uses: actions/checkout@v4 - - name: Hack Git CRLF for ocaml/setup-ocaml issue #529 - if: ${{ startsWith(matrix.os, 'windows-') }} - run: | - & "C:\Program Files\Git\bin\git.exe" config --system core.autocrlf input - - - name: Use OCaml ${{ matrix.ocaml-compiler }} - uses: ocaml/setup-ocaml@v2 + - name: Set-up OCaml + uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} - opam-depext-flags: --with-test opam-local-packages: ${{ matrix.local-packages }} - - run: opam depext conf-libev --install + - run: opam install conf-libev if: ${{ matrix.libev == true }} - - run: opam install lwt_luv lwt_react lwt --deps-only --with-test + - run: opam install lwt_react lwt --deps-only --with-test - - run: opam install lwt_ppx --deps-only --with-test + - run: opam install lwt_ppx lwt_ppx_let --deps-only --with-test if: ${{ matrix.ppx == true }} - - run: opam exec -- dune build --only-packages lwt_luv,lwt_react,lwt + - run: opam exec -- dune build --only-packages lwt_react,lwt - run: opam exec -- dune build --only-packages lwt_ppx if: ${{ matrix.ppx == true }} - - run: opam exec -- dune runtest --only-packages lwt_luv,lwt_react,lwt + - run: opam exec -- dune runtest --only-packages lwt_react,lwt - run: opam exec -- dune runtest --only-packages lwt_ppx if: ${{ matrix.ppx == true }} - - run: opam exec -- make ppx_let-test-deps ppx_let-test + - run: opam exec -- make ppx_let-test if: ${{ matrix.ppx == true }} + + lint-opam: + runs-on: ubuntu-latest + + steps: + - name: Checkout tree + uses: actions/checkout@v4 + + - name: Set-up OCaml + uses: ocaml/setup-ocaml@v3 + with: + ocaml-compiler: "5.2" + dune-cache: true + + - uses: ocaml/setup-ocaml/lint-opam@v3 diff --git a/CHANGES b/CHANGES index cb8d888fda..02f2e47374 100644 --- a/CHANGES +++ b/CHANGES @@ -1,34 +1,52 @@ -===== dev ===== +===== 5.7.0 ===== ====== Breaking API changes ====== - * Lwt_result.catch now takes a function (unit -> 'a t) rather than a promise ('a t) (#965) + * Lwt_result.catch now takes a function (unit -> 'a t) rather than a promise ('a t). (#965) - * Remove the deprecated Lwt.result type (use Stdlib.result instead) (#968) + * Remove the deprecated Lwt.result type (use Stdlib.result instead). (#968) - * Remove the deprecated Lwt.make_value and Lwt.make_result functions (use Ok and Error instead) (#968) + * Remove the deprecated Lwt.make_value and Lwt.make_result functions (use Ok and Error instead). (#968) - * Remove the deprecated and unsafe waiter_of_wakener (keep the waiter around when you create the wakener instead) (#968) + * Remove the deprecated and unsafe waiter_of_wakener (keep the waiter around when you create the wakener instead). (#968) - * Remove the deprecated Lwt_stream.on_termination and Lwt_stream.on_terminate (bind to Lwt_stream.closed instead) (#968) + * Remove the deprecated Lwt_stream.on_termination and Lwt_stream.on_terminate (bind to Lwt_stream.closed instead). (#968) - * Remove the deprecated Lwt_stream.result type (use Stdlib.result instead) (#968) + * Remove the deprecated Lwt_stream.result type (use Stdlib.result instead). (#968) - * Remove the deprecated Lwt_stream.map_exn function (use wrap_exn instead) (#968) + * Remove the deprecated Lwt_stream.map_exn function (use wrap_exn instead). (#968) ====== Additions ====== - * Lwt.reraise an exception raising function which preserves backtraces, recommended for use in Lwt.catch (#963) + * Lwt.Exception_filter for enabling/disabling system-exception catching. (#964) + + * Lwt.reraise an exception raising function which preserves backtraces, recommended for use in Lwt.catch. (#963) + * Expose Lwt_io.delete_recursively for deleting a directory and its content recursively. (#984, Antonin Décimo) * Lwt_preemptive.run_in_main_dont_wait to run a function in the main preemptive thread but without waiting for the result. (Kate Deplaix, #960) + * Lwt_unix.handle_signal and Lwt_engine.forwards_signal to allow other IO libraries (such as Eio) to share signal handlers. (Thomas Leonard, #993, #991) + ====== Build ====== * Remove unused dependency in dune file. (#969, Kate Deplaix) * Fix some compilation warnings for C stubs with OCaml 5. (#976, Antonin Décimo) +====== Fixes ====== + + * Use SA_ONSTACK on OCaml5 to avoid SIGSEGV. (Thomas Leonard, #993, #981) + + * Fix race in worker loop. (Thomas Leonard, #993, #994) + + * Fix marshall header size in Lwt_io.read_value. (Simmo Saan, #995) + +====== Misc ====== + + * Resolve paused promises only once in main loop. This lets Lwt.pause behave identical to Lwt_unix.yield. (#917, Christopher Zimmermann, Favonia) + + ===== 5.6.1 ===== ====== Fixes ====== diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..553b62f338 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,12 @@ +# Code of Conduct + +This project has adopted the [OCaml Code of Conduct](https://github.com/ocaml/code-of-conduct/blob/main/CODE_OF_CONDUCT.md). + +# Enforcement + +This project follows the OCaml Code of Conduct [enforcement policy](https://github.com/ocaml/code-of-conduct/blob/main/CODE_OF_CONDUCT.md#enforcement). + +To report any violations, please contact Jérôme +Vouillon, Raphaël Proust, Vincent Balat, Hugo Heuzard and Gabriel +Radanne at +(or some of them individually). diff --git a/Makefile b/Makefile index cb78adc4cd..29ef2277e1 100644 --- a/Makefile +++ b/Makefile @@ -76,10 +76,6 @@ ppx_let-test : dune build test/ppx_let/test.exe dune exec test/ppx_let/test.exe -.PHONY : ppx_let-test-deps -ppx_let-test-deps : - opam install --yes --unset-root ppx_let - .PHONY: clean clean : dune clean @@ -105,7 +101,6 @@ coverage : rm -rf _build/default/test/ppx_expect find _build -name '*.coverage' | xargs rm -f BISECT_ENABLE=yes dune runtest --force - BISECT_ENABLE=yes dune build @runtest_libuv --force bisect-ppx-report html $(EXPECTED_FILES) bisect-ppx-report summary @echo See _coverage/index.html diff --git a/docs/dune b/docs/dune new file mode 100644 index 0000000000..cb49e843ee --- /dev/null +++ b/docs/dune @@ -0,0 +1,3 @@ +(documentation + (package lwt) + (mld_files :standard)) diff --git a/docs/manual.mld b/docs/manual.mld new file mode 100644 index 0000000000..946867ce70 --- /dev/null +++ b/docs/manual.mld @@ -0,0 +1,974 @@ +{0 Lwt manual } + +{1 Introduction } + + When writing a program, a common developer's task is to handle I/O + operations. Indeed, most software interacts with several different + resources, such as: + +{ul + {- the kernel, by doing system calls,} + {- the user, by reading the keyboard, the mouse, or any input device,} + {- a graphical server, to build graphical user interface,} + {- other computers, by using the network,} + {- …and so on.}} + + When this list contains only one item, it is pretty easy to + handle. However as this list grows it becomes harder and harder to + make everything work together. Several choices have been proposed + to solve this problem: + +{ul + {- using a main loop, and integrating all components we are + interacting with into this main loop,} + {- using preemptive system threads.}} + + Both solutions have their advantages and their drawbacks. For the + first one, it may work, but it becomes very complicated to write + a piece of asynchronous sequential code. The typical example is + graphical user interfaces freezing and not redrawing themselves + because they are waiting for some blocking part of the code to + complete. + + If you already wrote code using preemptive threads, you should know + that doing it right with threads is a difficult job. Moreover, system + threads consume non-negligible resources, and so you can only launch + a limited number of threads at the same time. Thus, this is not a + general solution. + + [Lwt] offers a third alternative. It provides promises, which are + very fast: a promise is just a reference that will be filled asynchronously, + and calling a function that returns a promise does not require a new stack, + new process, or anything else. It is just a normal, fast, function call. + Promises compose nicely, allowing us to write highly asynchronous programs. + + In the first part, we will explain the concepts of [Lwt], then we will + describe the main modules [Lwt] consists of. + +{2 Finding examples } + + Additional sources of examples: + +{ul + {- {{: https://github.com/dkim/rwo-lwt#readme }Concurrent Programming with Lwt}} + {- {{: https://mirage.io/wiki/tutorial-lwt }Mirage Lwt Tutorial}} + {- {{: http://www.baturin.org/code/lwt-counter-server/ }Simple Server with Lwt}}} + +{1 The Lwt core library } + + In this section we describe the basics of [Lwt]. It is advised to + start [utop] and try the given code examples. + +{2 Lwt concepts } + + Let's take a classic function of the [Stdlib] module: + +{[ +# Stdlib.input_char;; +- : in_channel -> char = +]} + + This function will wait for a character to come on the given input + channel, and then return it. The problem with this function is that it is + blocking: while it is being executed, the whole program will be + blocked, and other events will not be handled until it returns. + + Now, let's look at the lwt equivalent: + +{[ +# Lwt_io.read_char;; +- : Lwt_io.input_channel -> char Lwt.t = +]} + + As you can see, it does not return just a character, but something of + type [char Lwt.t]. The type ['a Lwt.t] is the type + of promises that can be fulfilled later with a value of type ['a]. + [Lwt_io.read_char] will try to read a character from the + given input channel and {e immediately} return a promise, without + blocking, whether a character is available or not. If a character is + not available, the promise will just not be fulfilled {e yet}. + + Now, let's see what we can do with a [Lwt] promise. The following + code creates a pipe, creates a promise that is fulfilled with the result of + reading the input side: + +{[ +# let ic, oc = Lwt_io.pipe ();; +val ic : Lwt_io.input_channel = +val oc : Lwt_io.output_channel = +# let p = Lwt_io.read_char ic;; +val p : char Lwt.t = +]} + + We can now look at the state of our newly created promise: + +{[ +# Lwt.state p;; +- : char Lwt.state = Lwt.Sleep +]} + + A promise may be in one of the following states: + +{ul + {- [Return x], which means that the promise has been fulfilled + with the value [x]. This usually implies that the asynchronous + operation, that you started by calling the function that returned the + promise, has completed successfully.} + {- [Fail exn], which means that the promise has been rejected + with the exception [exn]. This usually means that the asynchronous + operation associated with the promise has failed.} + {- [Sleep], which means that the promise is has not yet been + fulfilled or rejected, so it is {e pending}.}} + + The above promise [p] is pending because there is nothing yet + to read from the pipe. Let's write something: + +{[ +# Lwt_io.write_char oc 'a';; +- : unit Lwt.t = +# Lwt.state p;; +- : char Lwt.state = Lwt.Return 'a' +]} + + So, after we write something, the reading promise has been fulfilled + with the value ['a']. + +{2 Primitives for promise creation } + + There are several primitives for creating [Lwt] promises. These + functions are located in the module [Lwt]. + + Here are the main primitives: + +{ul + {- [Lwt.return : 'a -> 'a Lwt.t] + creates a promise which is already fulfilled with the given value} + {- [Lwt.fail : exn -> 'a Lwt.t] + creates a promise which is already rejected with the given exception} + {- [Lwt.wait : unit -> 'a Lwt.t * 'a Lwt.u] + creates a pending promise, and returns it, paired with a resolver (of + type ['a Lwt.u]), which must be used to resolve (fulfill or reject) + the promise.}} + + To resolve a pending promise, use one of the following + functions: + +{ul + {- [Lwt.wakeup : 'a Lwt.u -> 'a -> unit] + fulfills the promise with a value.} + {- [Lwt.wakeup_exn : 'a Lwt.u -> exn -> unit] + rejects the promise with an exception.}} + + Note that it is an error to try to resolve the same promise twice. [Lwt] + will raise [Invalid_argument] if you try to do so. + + With this information, try to guess the result of each of the + following expressions: + +{[ +# Lwt.state (Lwt.return 42);; +# Lwt.state (Lwt.fail Exit);; +# let p, r = Lwt.wait ();; +# Lwt.state p;; +# Lwt.wakeup r 42;; +# Lwt.state p;; +# let p, r = Lwt.wait ();; +# Lwt.state p;; +# Lwt.wakeup_exn r Exit;; +# Lwt.state p;; +]} + +{3 Primitives for promise composition } + + The most important operation you need to know is [bind]: + +{[ +val bind : 'a Lwt.t -> ('a -> 'b Lwt.t) -> 'b Lwt.t +]} + + [bind p f] creates a promise which waits for [p] to become + become fulfilled, then passes the resulting value to [f]. If [p] is a + pending promise, then [bind p f] will be a pending promise too, + until [p] is resolved. If [p] is rejected, then the resulting + promise will be rejected with the same exception. For example, consider the + following expression: + +{[ +Lwt.bind + (Lwt_io.read_line Lwt_io.stdin) + (fun str -> Lwt_io.printlf "You typed %S" str) +]} + + This code will first wait for the user to enter a line of text, then + print a message on the standard output. + + Similarly to [bind], there is a function to handle the case + when [p] is rejected: + +{[ +val catch : (unit -> 'a Lwt.t) -> (exn -> 'a Lwt.t) -> 'a Lwt.t +]} + + [catch f g] will call [f ()], then wait for it to become + resolved, and if it was rejected with an exception [exn], call + [g exn] to handle it. Note that both exceptions raised with + [Pervasives.raise] and [Lwt.fail] are caught by + [catch]. + +{3 Cancelable promises } + + In some case, we may want to cancel a promise. For example, because it + has not resolved after a timeout. This can be done with cancelable + promises. To create a cancelable promise, you must use the + [Lwt.task] function: + +{[ +val task : unit -> 'a Lwt.t * 'a Lwt.u +]} + + It has the same semantics as [Lwt.wait], except that the + pending promise can be canceled with [Lwt.cancel]: + +{[ +val cancel : 'a Lwt.t -> unit +]} + + The promise will then be rejected with the exception + [Lwt.Canceled]. To execute a function when the promise is + canceled, you must use [Lwt.on_cancel]: + +{[ +val on_cancel : 'a Lwt.t -> (unit -> unit) -> unit +]} + + Note that canceling a promise does not automatically cancel the + asynchronous operation that is going to resolve it. It does, however, + prevent any further chained operations from running. The asynchronous + operation associated with a promise can only be canceled if its implementation + has taken care to set an [on_cancel] callback on the promise that + it returned to you. In practice, most operations (such as system calls) + can't be canceled once they are started anyway, so promise cancellation is + useful mainly for interrupting future operations once you know that a chain of + asynchronous operations will not be needed. + + It is also possible to cancel a promise which has not been + created directly by you with [Lwt.task]. In this case, the deepest + cancelable promise that the given promise depends on will be canceled. + + For example, consider the following code: + +{[ +# let p, r = Lwt.task ();; +val p : '_a Lwt.t = +val r : '_a Lwt.u = +# let p' = Lwt.bind p (fun x -> Lwt.return (x + 1));; +val p' : int Lwt.t = +]} + + Here, cancelling [p'] will in fact cancel [p], rejecting + it with [Lwt.Canceled]. [Lwt.bind] will then propagate the + exception forward to [p']: + +{[ +# Lwt.cancel p';; +- : unit = () +# Lwt.state p;; +- : int Lwt.state = Lwt.Fail Lwt.Canceled +# Lwt.state p';; +- : int Lwt.state = Lwt.Fail Lwt.Canceled +]} + + It is possible to prevent a promise from being canceled + by using the function [Lwt.protected]: + +{[ +val protected : 'a Lwt.t -> 'a Lwt.t +]} + + Canceling [(protected p)] will have no effect on [p]. + +{3 Primitives for concurrent composition } + + We now show how to compose several promises concurrently. The + main functions for this are in the [Lwt] module: [join], + [choose] and [pick]. + + The first one, [join] takes a list of promises and returns a promise + that is waiting for all of them to resolve: + +{[ +val join : unit Lwt.t list -> unit Lwt.t +]} + + Moreover, if at least one promise is rejected, [join l] will be rejected + with the same exception as the first one, after all the promises are resolved. + + Conversely, [choose] waits for at least {e one} promise to become + resolved, then resolves with the same value or exception: + +{[ +val choose : 'a Lwt.t list -> 'a Lwt.t +]} + + For example: + +{[ +# let p1, r1 = Lwt.wait ();; +val p1 : '_a Lwt.t = +val r1 : '_a Lwt.u = +# let p2, r2 = Lwt.wait ();; +val p2 : '_a Lwt.t = +val r2 : '_a Lwt.u = +# let p3 = Lwt.choose [p1; p2];; +val p3 : '_a Lwt.t = +# Lwt.state p3;; +- : '_a Lwt.state = Lwt.Sleep +# Lwt.wakeup r2 42;; +- : unit = () +# Lwt.state p3;; +- : int Lwt.state = Lwt.Return 42 +]} + + The last one, [pick], is the same as [choose], except that it tries to cancel + all other promises when one resolves. Promises created via [Lwt.wait()] are not cancellable + and are thus not cancelled. + +{3 Rules } + + A callback, like the [f] that you might pass to [Lwt.bind], is + an ordinary OCaml function. [Lwt] just handles ordering calls to these + functions. + + [Lwt] uses some preemptive threading internally, but all of your code + runs in the main thread, except when you explicitly opt into additional + threads with [Lwt_preemptive]. + + This simplifies reasoning about critical sections: all the code in one + callback cannot be interrupted by any of the code in another callback. + However, it also carries the danger that if a single callback takes a very + long time, it will not give [Lwt] a chance to run your other callbacks. + In particular: + +{ul + {- do not write functions that may take time to complete, without splitting + them up using [Lwt.pause] or performing some [Lwt] I/O,} + {- do not do I/O that may block, otherwise the whole program will + hang inside that callback. You must instead use the asynchronous I/O + operations provided by [Lwt].}} + +{2 The syntax extension } + + [Lwt] offers a PPX syntax extension which increases code readability and + makes coding using [Lwt] easier. The syntax extension is documented + in {!Ppx_lwt}. + + To use the PPX syntax extension, add the [lwt_ppx] package when + compiling: + +{[ +$ ocamlfind ocamlc -package lwt_ppx -linkpkg -o foo foo.ml +]} + + Or, in [utop]: + +{[ +# #require "lwt_ppx";; +]} + + [lwt_ppx] is distributed in a separate opam package of that same name. + + For a brief overview of the syntax, see the Correspondence table below. + +{3 Correspondence table } + +{table + {tr + {th Without Lwt} + {th With Lwt}} + {tr + {td {[let pattern_1 = expr_1 +and pattern_2 = expr2 +… +and pattern_n = expr_n in +expr]}} + {td {[let%lwt pattern_1 = expr_1 +and pattern_2 = expr2 +… +and pattern_n = expr_n in +expr]}}} + {tr + {td {[try expr with +| pattern_1 = expr_1 +| pattern_2 = expr2 +… +| pattern_n = expr_n]}} + {td {[try%lwt expr with +| pattern_1 = expr_1 +| pattern_2 = expr2 +… +| pattern_n = expr_n]}}} + {tr + {td {[match expr with +| pattern_1 = expr_1 +| pattern_2 = expr2 +… +| pattern_n = expr_n]}} + {td {[match%lwt expr with +| pattern_1 = expr_1 +| pattern_2 = expr2 +… +| pattern_n = expr_n]}}} + {tr + {td {[for ident = expr_init to expr_final do + expr +done]}} + {td {[for%lwt ident = expr_init to expr_final do + expr +done]}}} + {tr + {td {[while expr do expr done]}} + {td {[while%lwt expr do expr done]}}} + {tr + {td {[if expr then expr else expr]}} + {td {[if%lwt expr then expr else expr]}}} + {tr + {td {[assert expr]}} + {td {[assert%lwt expr]}}} + {tr + {td {[raise exn]}} + {td {[[%lwt raise exn]]}}}} + +{2 Backtrace support } + + If an exception is raised inside a callback called by Lwt, the backtrace + provided by OCaml will not be very useful. It will end inside the Lwt + scheduler instead of continuing into the code that started the operations that + led to the callback call. To avoid this, and get good backtraces from Lwt, use + the syntax extension. The [let%lwt] construct will properly propagate + backtraces. + + As always, to get backtraces from an OCaml program, you need to either declare + the environment variable [OCAMLRUNPARAM=b] or call + [Printexc.record_backtrace true] at the start of your program, and be + sure to compile it with [-g]. Most modern build systems add [-g] by + default. + +{2 [let*] syntax } + + To use Lwt with the [let*] syntax introduced in OCaml 4.08, you can open + the [Syntax] module: + +{[ +open Syntax +]} + + Then, you can write + +{[ +let* () = Lwt_io.printl "Hello," in +let* () = Lwt_io.printl "world!" in +Lwt.return () +]} + +{2 Other modules of the core library } + + The core library contains several modules that only depend on + [Lwt]. The following naming convention is used in [Lwt]: when a + function takes as argument a function, returning a promise, that is going + to be executed sequentially, it is suffixed with “[_s]”. And + when it is going to be executed concurrently, it is suffixed with + “[_p]”. For example, in the [Lwt_list] module we have: + +{[ +val map_s : ('a -> 'b Lwt.t) -> 'a list -> 'b list Lwt.t +val map_p : ('a -> 'b Lwt.t) -> 'a list -> 'b list Lwt.t +]} + +{3 Mutexes } + + [Lwt_mutex] provides mutexes for [Lwt]. Its use is almost the + same as the [Mutex] module of the thread library shipped with + OCaml. In general, programs using [Lwt] do not need a lot of + mutexes, because callbacks run without preempting each other. They are + only useful for synchronising or sequencing complex operations spread over + multiple callback calls. + +{3 Lists } + + The [Lwt_list] module defines iteration and scanning functions + over lists, similar to the ones of the [List] module, but using + functions that return a promise. For example: + +{[ +val iter_s : ('a -> unit Lwt.t) -> 'a list -> unit Lwt.t +val iter_p : ('a -> unit Lwt.t) -> 'a list -> unit Lwt.t +]} + + In [iter_s f l], [iter_s] will call f on each elements + of [l], waiting for resolution between each element. On the + contrary, in [iter_p f l], [iter_p] will call f on all + elements of [l], only then wait for all the promises to resolve. + +{3 Data streams } + + [Lwt] streams are used in a lot of places in [Lwt] and its + submodules. They offer a high-level interface to manipulate data flows. + + A stream is an object which returns elements sequentially and + lazily. Lazily means that the source of the stream is touched only for new + elements when needed. This module contains a lot of stream + transformation, iteration, and scanning functions. + + The common way of creating a stream is by using + [Lwt_stream.from] or by using [Lwt_stream.create]: + +{[ +val from : (unit -> 'a option Lwt.t) -> 'a Lwt_stream.t +val create : unit -> 'a Lwt_stream.t * ('a option -> unit) +]} + + As for streams of the standard library, [from] takes as + argument a function which is used to create new elements. + + [create] returns a function used to push new elements + into the stream and the stream which will receive them. + + For example: + +{[ +# let stream, push = Lwt_stream.create ();; +val stream : '_a Lwt_stream.t = +val push : '_a option -> unit = +# push (Some 1);; +- : unit = () +# push (Some 2);; +- : unit = () +# push (Some 3);; +- : unit = () +# Lwt.state (Lwt_stream.next stream);; +- : int Lwt.state = Lwt.Return 1 +# Lwt.state (Lwt_stream.next stream);; +- : int Lwt.state = Lwt.Return 2 +# Lwt.state (Lwt_stream.next stream);; +- : int Lwt.state = Lwt.Return 3 +# Lwt.state (Lwt_stream.next stream);; +- : int Lwt.state = Lwt.Sleep +]} + + Note that streams are consumable. Once you take an element from a + stream, it is removed from the stream. So, if you want to iterate two times + over a stream, you may consider “cloning” it, with + [Lwt_stream.clone]. Cloned stream will return the same + elements in the same order. Consuming one will not consume the other. + For example: + +{[ +# let s = Lwt_stream.of_list [1; 2];; +val s : int Lwt_stream.t = +# let s' = Lwt_stream.clone s;; +val s' : int Lwt_stream.t = +# Lwt.state (Lwt_stream.next s);; +- : int Lwt.state = Lwt.Return 1 +# Lwt.state (Lwt_stream.next s);; +- : int Lwt.state = Lwt.Return 2 +# Lwt.state (Lwt_stream.next s');; +- : int Lwt.state = Lwt.Return 1 +# Lwt.state (Lwt_stream.next s');; +- : int Lwt.state = Lwt.Return 2 +]} + +{3 Mailbox variables } + + The [Lwt_mvar] module provides mailbox variables. A mailbox + variable, also called a “mvar”, is a cell which may contain 0 or 1 + element. If it contains no elements, we say that the mvar is empty, + if it contains one, we say that it is full. Adding an element to a + full mvar will block until one is taken. Taking an element from an + empty mvar will block until one is added. + + Mailbox variables are commonly used to pass messages between chains of + callbacks being executed concurrently. + + Note that a mailbox variable can be seen as a pushable stream with a + limited memory. + +{1 Running an Lwt program } + + An [Lwt] computation you have created will give you something of type + [Lwt.t], a promise. However, even though you have the promise, the + computation may not have run yet, and the promise might still be pending. + + For example if your program is just: + +{[ +let _ = Lwt_io.printl "Hello, world!" +]} + + you have no guarantee that the promise for writing ["Hello, world!"] + on the terminal will be resolved before the program exits. In order + to wait for the promise to resolve, you have to call the function + [Lwt_main.run]: + +{[ +val Lwt_main.run : 'a Lwt.t -> 'a +]} + + This function waits for the given promise to resolve and returns + its result. In fact it does more than that; it also runs the + scheduler which is responsible for making asynchronous computations progress + when events are received from the outside world. + + So basically, when you write a [Lwt] program, you must call + [Lwt_main.run] on your top-level, outer-most promise. For instance: + +{[ +let () = Lwt_main.run (Lwt_io.printl "Hello, world!") +]} + + Note that you must not make nested calls to [Lwt_main.run]. It + cannot be used anywhere else to get the result of a promise. + +{1 The [lwt.unix] library } + + The package [lwt.unix] contains all [Unix]-dependent + modules of [Lwt]. Among all its features, it implements Lwt-friendly, + non-blocking versions of functions of the OCaml standard and Unix libraries. + +{2 Unix primitives } + + Module [Lwt_unix] provides non-blocking system calls. For example, + the [Lwt] counterpart of [Unix.read] is: + +{[ +val read : file_descr -> string -> int -> int -> int Lwt.t +]} + + [Lwt_io] provides features similar to buffered channels of + the standard library (of type [in_channel] or + [out_channel]), but with non-blocking semantics. + + [Lwt_gc] allows you to register a finalizer that returns a + promise. At the end of the program, [Lwt] will wait for all these + finalizers to resolve. + +{2 The Lwt scheduler } + + Operations doing I/O have to be resumed when some events are received by + the process, so they can resolve their associated pending promises. + For example, when you read from a file descriptor, you + may have to wait for the file descriptor to become readable if no + data are immediately available on it. + + [Lwt] contains a scheduler which is responsible for managing + multiple operations waiting for events, and restarting them when needed. + This scheduler is implemented by the two modules [Lwt_engine] + and [Lwt_main]. [Lwt_engine] is a low-level module, it + provides a signature for custom I/O multiplexers as well as two built-in + implementations, [libev] and [select]. The signature is given by the + class [Lwt_engine.t]. + + [libev] is used by default on Linux, because it supports any + number of file descriptors, while [select] supports only 1024. [libev] + is also much more efficient. On Windows, [Unix.select] is used because + [libev] does not work properly. The user may change the backend in use at + any time. + + If you see an [Invalid_argument] error on [Unix.select], it + may be because the 1024 file descriptor limit was exceeded. Try + switching to [libev], if possible. + + The engine can also be used directly in order to integrate other + libraries with [Lwt]. For example, [GTK] needs to be notified + when some events are received. If you use [Lwt] with [GTK] + you need to use the [Lwt] scheduler to monitor [GTK] + sources. This is what is done by the [Lwt_glib] library. + + The [Lwt_main] module contains the {e main loop} of + [Lwt]. It is run by calling the function [Lwt_main.run]: + +{[ +val Lwt_main.run : 'a Lwt.t -> 'a +]} + + This function continuously runs the scheduler until the promise passed + as argument is resolved. + + To make sure [Lwt] is compiled with [libev] support, + tell opam that the library is available on the system by installing the + {{: http://opam.ocaml.org/packages/conf-libev/conf-libev.4-11/ }conf-libev} + package. You may get the actual library with your system package manager: + +{ul + {- [brew install libev] on MacOSX,} + {- [apt-get install libev-dev] on Debian/Ubuntu, or} + {- [yum install libev-devel] on CentOS, which requires to set + [export C_INCLUDE_PATH=/usr/include/libev/] and + [export LIBRARY_PATH=/usr/lib64/] before calling + [opam install conf-libev].}} + + +{2 Logging } + + For logging, we recommend the [logs] package from opam, which includes an + Lwt-aware module [Logs_lwt]. + +{1 The Lwt.react library } + + The [Lwt_react] module provides helpers for using the [react] + library with [Lwt]. It extends the [React] module by adding + [Lwt]-specific functions. It can be used as a replacement of + [React]. For example you can add at the beginning of your + program: + +{[ +open Lwt_react +]} + + instead of: + +{[ +open React +]} + + or: + +{[ +module React = Lwt_react +]} + + Among the added functionalities we have [Lwt_react.E.next], which + takes an event and returns a promise which will be pending until the next + occurrence of this event. For example: + +{[ +# open Lwt_react;; +# let event, push = E.create ();; +val event : '_a React.event = +val push : '_a -> unit = +# let p = E.next event;; +val p : '_a Lwt.t = +# Lwt.state p;; +- : '_a Lwt.state = Lwt.Sleep +# push 42;; +- : unit = () +# Lwt.state p;; +- : int Lwt.state = Lwt.Return 42 +]} + + Another interesting feature is the ability to limit events + (resp. signals) from occurring (resp. changing) too often. For example, + suppose you are doing a program which displays something on the screen + each time a signal changes. If at some point the signal changes 1000 + times per second, you probably don't want to render it 1000 times per + second. For that you use [Lwt_react.S.limit]: + +{[ +val limit : (unit -> unit Lwt.t) -> 'a React.signal -> 'a React.signal +]} + + [Lwt_react.S.limit f signal] returns a signal which varies as + [signal] except that two consecutive updates are separated by a + call to [f]. For example if [f] returns a promise which is pending + for 0.1 seconds, then there will be no more than 10 changes per + second: + +{[ +open Lwt_react + +let draw x = + (* Draw the screen *) + … + +let () = + (* The signal we are interested in: *) + let signal = … in + + (* The limited signal: *) + let signal' = S.limit (fun () -> Lwt_unix.sleep 0.1) signal in + + (* Redraw the screen each time the limited signal change: *) + S.notify_p draw signal' +]} + +{1 Other libraries } + +{2 Parallelise computations to other cores } + + If you have some compute-intensive steps within your program, you can execute + them on a separate core. You can get performance benefits from the + parallelisation. In addition, whilst your compute-intensive function is running + on a different core, your normal I/O-bound tasks continue running on the + original core. + + The module {!Lwt_domain} from the [lwt_domain] package provides all the + necessary helpers to achieve this. It is based on the [Domainslib] library + and uses similar concepts (such as tasks and pools). + + First, you need to create a task pool: + +{[ +val setup_pool : ?name:string -> int -> pool +]} + + Then you simple detach the function calls to the created pool: + +{[ +val detach : pool -> ('a -> 'b) -> 'a -> 'b Lwt.t +]} + + The returned promise resolves as soon as the function returns. + +{2 Detaching computation to preemptive threads } + + It may happen that you want to run a function which will take time to + compute or that you want to use a blocking function that cannot be + used in a non-blocking way. For these situations, [Lwt] allows you to + {e detach} the computation to a preemptive thread. + + This is done by the module [Lwt_preemptive] of the + [lwt.unix] package which maintains a pool of system + threads. The main function is: + +{[ +val detach : ('a -> 'b) -> 'a -> 'b Lwt.t +]} + + [detach f x] will execute [f x] in another thread and + return a pending promise, usable from the main thread, which will be fulfilled + with the result of the preemptive thread. + + If you want to trigger some [Lwt] operations from your detached thread, + you have to call back into the main thread using + [Lwt_preemptive.run_in_main]: + +{[ +val run_in_main : (unit -> 'a Lwt.t) -> 'a +]} + + This is roughly the equivalent of [Lwt.main_run], but for detached + threads, rather than for the whole process. Note that you must not call + [Lwt_main.run] in a detached thread. + +{2 SSL support } + + The library [Lwt_ssl] allows use of SSL asynchronously. + +{1 Writing stubs using [Lwt] } + +{2 Thread-safe notifications } + + If you want to notify the main thread from another thread, you can use the [Lwt] + thread safe notification system. First you need to create a notification identifier + (which is just an integer) from the OCaml side using the + [Lwt_unix.make_notification] function, then you can send it from either the + OCaml code with [Lwt_unix.send_notification] function, or from the C code using + the function [lwt_unix_send_notification] (defined in [lwt_unix_.h]). + + Notifications are received and processed asynchronously by the main thread. + +{2 Jobs } + + For operations that cannot be executed asynchronously, [Lwt] + uses a system of jobs that can be executed in a different threads. A + job is composed of three functions: + +{ul + {- A stub function to create the job. It must allocate a new job + structure and fill its [worker] and [result] fields. This + function is executed in the main thread. + The return type for the OCaml external must be of the form ['a job].} + {- A function which executes the job. This one may be executed asynchronously + in another thread. This function must not: + {ul + {- access or allocate OCaml block values (tuples, strings, …),} + {- call OCaml code.}}} + {- A function which reads the result of the job, frees resources and + returns the result as an OCaml value. This function is executed in + the main thread.}} + + With [Lwt < 2.3.3], 4 functions (including 3 stubs) were + required. It is still possible to use this mode but it is + deprecated. + + We show as example the implementation of [Lwt_unix.mkdir]. On the C + side we have: + +{@c[/**/ +/* Structure holding informations for calling [mkdir]. */ +struct job_mkdir { + /* Informations used by lwt. + It must be the first field of the structure. */ + struct lwt_unix_job job; + /* This field store the result of the call. */ + int result; + /* This field store the value of [errno] after the call. */ + int errno_copy; + /* Pointer to a copy of the path parameter. */ + char* path; + /* Copy of the mode parameter. */ + int mode; + /* Buffer for storing the path. */ + char data[]; +}; + +/* The function calling [mkdir]. */ +static void worker_mkdir(struct job_mkdir* job) +{ + /* Perform the blocking call. */ + job->result = mkdir(job->path, job->mode); + /* Save the value of errno. */ + job->errno_copy = errno; +} + +/* The function building the caml result. */ +static value result_mkdir(struct job_mkdir* job) +{ + /* Check for errors. */ + if (job->result < 0) { + /* Save the value of errno so we can use it + once the job has been freed. */ + int error = job->errno_copy; + /* Copy the contents of job->path into a caml string. */ + value string_argument = caml_copy_string(job->path); + /* Free the job structure. */ + lwt_unix_free_job(&job->job); + /* Raise the error. */ + unix_error(error, "mkdir", string_argument); + } + /* Free the job structure. */ + lwt_unix_free_job(&job->job); + /* Return the result. */ + return Val_unit; +} + +/* The stub creating the job structure. */ +CAMLprim value lwt_unix_mkdir_job(value path, value mode) +{ + /* Get the length of the path parameter. */ + mlsize_t len_path = caml_string_length(path) + 1; + /* Allocate a new job. */ + struct job_mkdir* job = + (struct job_mkdir*)lwt_unix_new_plus(struct job_mkdir, len_path); + /* Set the offset of the path parameter inside the job structure. */ + job->path = job->data; + /* Copy the path parameter inside the job structure. */ + memcpy(job->path, String_val(path), len_path); + /* Initialize function fields. */ + job->job.worker = (lwt_unix_job_worker)worker_mkdir; + job->job.result = (lwt_unix_job_result)result_mkdir; + /* Copy the mode parameter. */ + job->mode = Int_val(mode); + /* Wrap the structure into a caml value. */ + return lwt_unix_alloc_job(&job->job); +} +]} + + and on the ocaml side: + +{[ +(* The stub for creating the job. *) +external mkdir_job : string -> int -> unit job = "lwt_unix_mkdir_job" + +(* The ocaml function. *) +let mkdir name perms = Lwt_unix.run_job (mkdir_job name perms) +]} diff --git a/docs/manual.wiki b/docs/manual.wiki deleted file mode 100644 index c78e59f3c7..0000000000 --- a/docs/manual.wiki +++ /dev/null @@ -1,881 +0,0 @@ -= Lwt manual = - -== Introduction == - - When writing a program, a common developer's task is to handle I/O - operations. Indeed, most software interacts with several different - resources, such as: - - * the kernel, by doing system calls, - * the user, by reading the keyboard, the mouse, or any input device, - * a graphical server, to build graphical user interface, - * other computers, by using the network, - * ...and so on. - - When this list contains only one item, it is pretty easy to - handle. However as this list grows it becomes harder and harder to - make everything work together. Several choices have been proposed - to solve this problem: - - * using a main loop, and integrating all components we are - interacting with into this main loop. - * using preemptive system threads - - Both solutions have their advantages and their drawbacks. For the - first one, it may work, but it becomes very complicated to write - a piece of asynchronous sequential code. The typical example is - graphical user interfaces freezing and not redrawing themselves - because they are waiting for some blocking part of the code to - complete. - - If you already wrote code using preemptive threads, you should know - that doing it right with threads is a difficult job. Moreover, system - threads consume non-negligible resources, and so you can only launch - a limited number of threads at the same time. Thus, this is not a - general solution. - - {{{Lwt}}} offers a third alternative. It provides promises, which are - very fast: a promise is just a reference that will be filled asynchronously, - and calling a function that returns a promise does not require a new stack, - new process, or anything else. It is just a normal, fast, function call. - Promises compose nicely, allowing us to write highly asynchronous programs. - - In the first part, we will explain the concepts of {{{Lwt}}}, then we will - describe the main modules {{{Lwt}}} consists of. - -=== Finding examples === - - Additional sources of examples: - - * [[https://github.com/dkim/rwo-lwt#readme|Concurrent Programming with Lwt]] - * [[https://mirage.io/wiki/tutorial-lwt|Mirage Lwt Tutorial]] - * [[http://www.baturin.org/code/lwt-counter-server/|Simple Server with Lwt]] - -== The Lwt core library == - - In this section we describe the basics of {{{Lwt}}}. It is advised to - start {{{utop}}} and try the given code examples. - -=== Lwt concepts === - - Let's take a classic function of the {{{Stdlib}}} module: - -< char = ->> - - This function will wait for a character to come on the given input - channel, and then return it. The problem with this function is that it is - blocking: while it is being executed, the whole program will be - blocked, and other events will not be handled until it returns. - - Now, let's look at the lwt equivalent: - -< char Lwt.t = ->> - - As you can see, it does not return just a character, but something of - type {{{char Lwt.t}}}. The type {{{'a Lwt.t}}} is the type - of promises that can be fulfilled later with a value of type {{{'a}}}. - {{{Lwt_io.read_char}}} will try to read a character from the - given input channel and //immediately// return a promise, without - blocking, whether a character is available or not. If a character is - not available, the promise will just not be fulfilled //yet//. - - Now, let's see what we can do with a {{{Lwt}}} promise. The following - code creates a pipe, creates a promise that is fulfilled with the result of - reading the input side: - -< -val oc : Lwt_io.output_channel = -# let p = Lwt_io.read_char ic;; -val p : char Lwt.t = ->> - - We can now look at the state of our newly created promise: - -<> - - A promise may be in one of the following states: - - * {{{Return x}}}, which means that the promise has been fulfilled - with the value {{{x}}}. This usually implies that the asynchronous - operation, that you started by calling the function that returned the - promise, has completed successfully. - * {{{Fail exn}}}, which means that the promise has been rejected - with the exception {{{exn}}}. This usually means that the asynchronous - operation associated with the promise has failed. - * {{{Sleep}}}, which means that the promise is has not yet been - fulfilled or rejected, so it is //pending//. - - The above promise {{{p}}} is pending because there is nothing yet - to read from the pipe. Let's write something: - -< -# Lwt.state p;; -- : char Lwt.state = Lwt.Return 'a' ->> - - So, after we write something, the reading promise has been fulfilled - with the value {{{'a'}}}. - -=== Primitives for promise creation === - - There are several primitives for creating {{{Lwt}}} promises. These - functions are located in the module {{{Lwt}}}. - - Here are the main primitives: - - * {{{Lwt.return : 'a -> 'a Lwt.t}}} - \\ - creates a promise which is already fulfilled with the given value - * {{{Lwt.fail : exn -> 'a Lwt.t}}} - \\ - creates a promise which is already rejected with the given exception - * {{{Lwt.wait : unit -> 'a Lwt.t * 'a Lwt.u}}} - \\ - creates a pending promise, and returns it, paired with a resolver (of - type {{{'a Lwt.u}}}), which must be used to resolve (fulfill or reject) - the promise. - - To resolve a pending promise, use one of the following - functions: - - * {{{Lwt.wakeup : 'a Lwt.u -> 'a -> unit}}} - \\ - fulfills the promise with a value. - * {{{Lwt.wakeup_exn : 'a Lwt.u -> exn -> unit}}} - \\ - rejects the promise with an exception. - - Note that it is an error to try to resolve the same promise twice. {{{Lwt}}} - will raise {{{Invalid_argument}}} if you try to do so. - - With this information, try to guess the result of each of the - following expressions: - -<> - -==== Primitives for promise composition ==== - - The most important operation you need to know is {{{bind}}}: - -< ('a -> 'b Lwt.t) -> 'b Lwt.t ->> - - {{{bind p f}}} creates a promise which waits for {{{p}}} to become - become fulfilled, then passes the resulting value to {{{f}}}. If {{{p}}} is a - pending promise, then {{{bind p f}}} will be a pending promise too, - until {{{p}}} is resolved. If {{{p}}} is rejected, then the resulting - promise will be rejected with the same exception. For example, consider the - following expression: - -< Lwt_io.printlf "You typed %S" str) ->> - - This code will first wait for the user to enter a line of text, then - print a message on the standard output. - - Similarly to {{{bind}}}, there is a function to handle the case - when {{{p}}} is rejected: - -< 'a Lwt.t) -> (exn -> 'a Lwt.t) -> 'a Lwt.t ->> - - {{{catch f g}}} will call {{{f ()}}}, then wait for it to become - resolved, and if it was rejected with an exception {{{exn}}}, call - {{{g exn}}} to handle it. Note that both exceptions raised with - {{{Stdlib.raise}}} and {{{Lwt.fail}}} are caught by - {{{catch}}}. - -==== Cancelable promises ==== - - In some case, we may want to cancel a promise. For example, because it - has not resolved after a timeout. This can be done with cancelable - promises. To create a cancelable promise, you must use the - {{{Lwt.task}}} function: - -< 'a Lwt.t * 'a Lwt.u ->> - - It has the same semantics as {{{Lwt.wait}}}, except that the - pending promise can be canceled with {{{Lwt.cancel}}}: - -< unit ->> - - The promise will then be rejected with the exception - {{{Lwt.Canceled}}}. To execute a function when the promise is - canceled, you must use {{{Lwt.on_cancel}}}: - -< (unit -> unit) -> unit ->> - - Note that canceling a promise does not automatically cancel the - asynchronous operation that is going to resolve it. It does, however, - prevent any further chained operations from running. The asynchronous - operation associated with a promise can only be canceled if its implementation - has taken care to set an {{{on_cancel}}} callback on the promise that - it returned to you. In practice, most operations (such as system calls) - can't be canceled once they are started anyway, so promise cancellation is - useful mainly for interrupting future operations once you know that a chain of - asynchronous operations will not be needed. - - It is also possible to cancel a promise which has not been - created directly by you with {{{Lwt.task}}}. In this case, the deepest - cancelable promise that the given promise depends on will be canceled. - - For example, consider the following code: - -< -val r : '_a Lwt.u = -# let p' = Lwt.bind p (fun x -> Lwt.return (x + 1));; -val p' : int Lwt.t = ->> - - Here, cancelling {{{p'}}} will in fact cancel {{{p}}}, rejecting - it with {{{Lwt.Canceled}}}. {{{Lwt.bind}}} will then propagate the - exception forward to {{{p'}}}: - -<> - - It is possible to prevent a promise from being canceled - by using the function {{{Lwt.protected}}}: - -< 'a Lwt.t ->> - - Canceling {{{(protected p)}}} will have no effect on {{{p}}}. - -==== Primitives for concurrent composition ==== - - We now show how to compose several promises concurrently. The - main functions for this are in the {{{Lwt}}} module: {{{join}}}, - {{{choose}}} and {{{pick}}}. - - The first one, {{{join}}} takes a list of promises and returns a promise - that is waiting for all of them to resolve: - -< unit Lwt.t ->> - - Moreover, if at least one promise is rejected, {{{join l}}} will be rejected - with the same exception as the first one, after all the promises are resolved. - - Conversely, {{{choose}}} waits for at least //one// promise to become - resolved, then resolves with the same value or exception: - -< 'a Lwt.t ->> - - For example: - -< -val r1 : '_a Lwt.u = -# let p2, r2 = Lwt.wait ();; -val p2 : '_a Lwt.t = -val r2 : '_a Lwt.u = -# let p3 = Lwt.choose [p1; p2];; -val p3 : '_a Lwt.t = -# Lwt.state p3;; -- : '_a Lwt.state = Lwt.Sleep -# Lwt.wakeup r2 42;; -- : unit = () -# Lwt.state p3;; -- : int Lwt.state = Lwt.Return 42 ->> - - The last one, {{{pick}}}, is the same as {{{choose}}}, except that it tries to cancel - all other promises when one resolves. Promises created via {{{Lwt.wait()}}} are not cancellable - and are thus not cancelled. - -==== Rules ==== - - A callback, like the {{{f}}} that you might pass to {{{Lwt.bind}}}, is - an ordinary OCaml function. {{{Lwt}}} just handles ordering calls to these - functions. - - {{{Lwt}}} uses some preemptive threading internally, but all of your code - runs in the main thread, except when you explicitly opt into additional - threads with {{{Lwt_preemptive}}}. - - This simplifies reasoning about critical sections: all the code in one - callback cannot be interrupted by any of the code in another callback. - However, it also carries the danger that if a single callback takes a very - long time, it will not give {{{Lwt}}} a chance to run your other callbacks. - In particular: - - * do not write functions that may take time to complete, without splitting - them up using {{{Lwt.pause}}} or performing some {{{Lwt}}} I/O, - * do not do I/O that may block, otherwise the whole program will - hang inside that callback. You must instead use the asynchronous I/O - operations provided by {{{Lwt}}}. - -=== The syntax extension === - - {{{Lwt}}} offers a PPX syntax extension which increases code readability and - makes coding using {{{Lwt}}} easier. The syntax extension is documented - <>. - - To use the PPX syntax extension, add the {{{lwt_ppx}}} package when - compiling: - -<> - - Or, in {{{utop}}}: - -<> - - {{{lwt_ppx}}} is distributed in a separate opam package of that same name. - - For a brief overview of the syntax, see the Correspondence table below. - -==== Correspondence table ==== - - |= without {{{Lwt}}} |= with {{{Lwt}}} | - | | | - | {{{let}}} //pattern,,1,,// {{{=}}} //expr,,1,,// | {{{let%lwt}}} //pattern,,1,,// {{{=}}} //expr,,1,,// | - | {{{and}}} //pattern,,2,,// {{{=}}} //expr,,2,,// | {{{and}}} //pattern,,2,,// {{{=}}} //expr,,2,,// | - | ... | ... | - | {{{and}}} //pattern,,n,,// {{{=}}} //expr,,n,,// {{{in}}} | {{{and}}} //pattern,,n,,// {{{=}}} //expr,,n,,// {{{in}}} | - | //expr// | //expr// | - | | | - | {{{try}}} | {{{try%lwt}}} | - | // expr// | // expr// | - | {{{with}}} | {{{with}}} | - | // // {{{|}}} //pattern,,1,,// {{{->}}} //expr,,1,,// | // // {{{|}}} //pattern,,1,,// {{{->}}} //expr,,1,,// | - | // // {{{|}}} //pattern,,2,,// {{{->}}} //expr,,2,,// | // // {{{|}}} //pattern,,2,,// {{{->}}} //expr,,2,,// | - | // // ... | // // ... | - | // // {{{|}}} //pattern,,n,,// {{{->}}} //expr,,n,,// | // // {{{|}}} //pattern,,n,,// {{{->}}} //expr,,n,,// | - | | | - | {{{for}}} //ident// {{{=}}} //expr,,init,,// {{{to}}} //expr,,final,,// {{{do}}} | {{{for%lwt}}} //ident// {{{=}}} //expr,,init,,// {{{to}}} //expr,,final,,// {{{do}}} | - | // expr// | // expr// | - | {{{done}}} | {{{done}}} | - | | | - | {{{while}}} //expr// {{{do}}} | {{{while%lwt}}} //expr// {{{do}}} | - | // expr// | // expr// | - | {{{done}}} | {{{done}}} | - | | | - | {{{assert}}} //expr// | {{{assert%lwt}}} //expr// | - | | | - | {{{match}}} //expr// {{{with}}} | {{{match%lwt}}} //expr// {{{with}}} | - | // // {{{|}}} //pattern,,1,,// {{{->}}} //expr,,1,,// | // // {{{|}}} //pattern,,1,,// {{{->}}} //expr,,1,,// | - | // // {{{|}}} //pattern,,2,,// {{{->}}} //expr,,2,,// | // // {{{|}}} //pattern,,2,,// {{{->}}} //expr,,2,,// | - | // // ... | // // ... | - | // // {{{|}}} //pattern,,n,,// {{{->}}} //expr,,n,,// | // // {{{|}}} //pattern,,n,,// {{{->}}} //expr,,n,,// | - | | | - | {{{if}}} //expr// {{{then}}} //expr// {{{else}}} //expr// | {{{if%lwt}}} //expr// {{{then}}} //expr// {{{else}}} //expr// | - | | | - | {{{raise}}} //exn// | {{{[%lwt raise}}} //exn//{{{]}}} | - -=== Backtrace support === - - If an exception is raised inside a callback called by Lwt, the backtrace - provided by OCaml will not be very useful. It will end inside the Lwt - scheduler instead of continuing into the code that started the operations that - led to the callback call. To avoid this, and get good backtraces from Lwt, use - the syntax extension. The {{{let%lwt}}} construct will properly propagate - backtraces. - - As always, to get backtraces from an OCaml program, you need to either declare - the environment variable {{{OCAMLRUNPARAM=b}}} or call - {{{Printexc.record_backtrace true}}} at the start of your program, and be - sure to compile it with {{{-g}}}. Most modern build systems add {{{-g}}} by - default. - -=== {{{let*}}} syntax === - - To use Lwt with the {{{let*}}} syntax introduced in OCaml 4.08, you can open - the {{{Syntax}}} module: - -<> - - Then, you can write - -<> - -=== Other modules of the core library === - - The core library contains several modules that only depend on - {{{Lwt}}}. The following naming convention is used in {{{Lwt}}}: when a - function takes as argument a function, returning a promise, that is going - to be executed sequentially, it is suffixed with ``{{{_s}}}''. And - when it is going to be executed concurrently, it is suffixed with - ``{{{_p}}}''. For example, in the {{{Lwt_list}}} module we have: - -< 'b Lwt.t) -> 'a list -> 'b list Lwt.t -val map_p : ('a -> 'b Lwt.t) -> 'a list -> 'b list Lwt.t ->> - -==== Mutexes ==== - - {{{Lwt_mutex}}} provides mutexes for {{{Lwt}}}. Its use is almost the - same as the {{{Mutex}}} module of the thread library shipped with - OCaml. In general, programs using {{{Lwt}}} do not need a lot of - mutexes, because callbacks run without preempting each other. They are - only useful for synchronising or sequencing complex operations spread over - multiple callback calls. - -==== Lists ==== - - The {{{Lwt_list}}} module defines iteration and scanning functions - over lists, similar to the ones of the {{{List}}} module, but using - functions that return a promise. For example: - -< unit Lwt.t) -> 'a list -> unit Lwt.t -val iter_p : ('a -> unit Lwt.t) -> 'a list -> unit Lwt.t ->> - - In {{{iter_s f l}}}, {{{iter_s}}} will call f on each elements - of {{{l}}}, waiting for resolution between each element. On the - contrary, in {{{iter_p f l}}}, {{{iter_p}}} will call f on all - elements of {{{l}}}, only then wait for all the promises to resolve. - -==== Data streams ==== - - {{{Lwt}}} streams are used in a lot of places in {{{Lwt}}} and its - submodules. They offer a high-level interface to manipulate data flows. - - A stream is an object which returns elements sequentially and - lazily. Lazily means that the source of the stream is touched only for new - elements when needed. This module contains a lot of stream - transformation, iteration, and scanning functions. - - The common way of creating a stream is by using - {{{Lwt_stream.from}}} or by using {{{Lwt_stream.create}}}: - -< 'a option Lwt.t) -> 'a Lwt_stream.t -val create : unit -> 'a Lwt_stream.t * ('a option -> unit) ->> - - As for streams of the standard library, {{{from}}} takes as - argument a function which is used to create new elements. - - {{{create}}} returns a function used to push new elements - into the stream and the stream which will receive them. - - For example: - -< -val push : '_a option -> unit = -# push (Some 1);; -- : unit = () -# push (Some 2);; -- : unit = () -# push (Some 3);; -- : unit = () -# Lwt.state (Lwt_stream.next stream);; -- : int Lwt.state = Lwt.Return 1 -# Lwt.state (Lwt_stream.next stream);; -- : int Lwt.state = Lwt.Return 2 -# Lwt.state (Lwt_stream.next stream);; -- : int Lwt.state = Lwt.Return 3 -# Lwt.state (Lwt_stream.next stream);; -- : int Lwt.state = Lwt.Sleep ->> - - Note that streams are consumable. Once you take an element from a - stream, it is removed from the stream. So, if you want to iterate two times - over a stream, you may consider ``cloning'' it, with - {{{Lwt_stream.clone}}}. Cloned stream will return the same - elements in the same order. Consuming one will not consume the other. - For example: - -< -# let s' = Lwt_stream.clone s;; -val s' : int Lwt_stream.t = -# Lwt.state (Lwt_stream.next s);; -- : int Lwt.state = Lwt.Return 1 -# Lwt.state (Lwt_stream.next s);; -- : int Lwt.state = Lwt.Return 2 -# Lwt.state (Lwt_stream.next s');; -- : int Lwt.state = Lwt.Return 1 -# Lwt.state (Lwt_stream.next s');; -- : int Lwt.state = Lwt.Return 2 ->> - -==== Mailbox variables ==== - - The {{{Lwt_mvar}}} module provides mailbox variables. A mailbox - variable, also called a ``mvar'', is a cell which may contain 0 or 1 - element. If it contains no elements, we say that the mvar is empty, - if it contains one, we say that it is full. Adding an element to a - full mvar will block until one is taken. Taking an element from an - empty mvar will block until one is added. - - Mailbox variables are commonly used to pass messages between chains of - callbacks being executed concurrently. - - Note that a mailbox variable can be seen as a pushable stream with a - limited memory. - -== Running an Lwt program == - - An {{{Lwt}}} computation you have created will give you something of type - {{{Lwt.t}}}, a promise. However, even though you have the promise, the - computation may not have run yet, and the promise might still be pending. - - For example if your program is just: - -<> - - you have no guarantee that the promise for writing {{{"Hello, world!"}}} - on the terminal will be resolved before the program exits. In order - to wait for the promise to resolve, you have to call the function - {{{Lwt_main.run}}}: - -< 'a ->> - - This function waits for the given promise to resolve and returns - its result. In fact it does more than that; it also runs the - scheduler which is responsible for making asynchronous computations progress - when events are received from the outside world. - - So basically, when you write a {{{Lwt}}} program, you must call - {{{Lwt_main.run}}} on your top-level, outer-most promise. For instance: - -<> - - Note that you must not make nested calls to {{{Lwt_main.run}}}. It - cannot be used anywhere else to get the result of a promise. - -== The {{{lwt.unix}}} library == - - The package {{{lwt.unix}}} contains all {{{Unix}}}-dependent - modules of {{{Lwt}}}. Among all its features, it implements Lwt-friendly, - non-blocking versions of functions of the OCaml standard and Unix libraries. - -=== Unix primitives === - - Module {{{Lwt_unix}}} provides non-blocking system calls. For example, - the {{{Lwt}}} counterpart of {{{Unix.read}}} is: - -< string -> int -> int -> int Lwt.t ->> - - {{{Lwt_io}}} provides features similar to buffered channels of - the standard library (of type {{{in_channel}}} or - {{{out_channel}}}), but with non-blocking semantics. - - {{{Lwt_gc}}} allows you to register a finalizer that returns a - promise. At the end of the program, {{{Lwt}}} will wait for all these - finalizers to resolve. - -=== The Lwt scheduler === - - Operations doing I/O have to be resumed when some events are received by - the process, so they can resolve their associated pending promises. - For example, when you read from a file descriptor, you - may have to wait for the file descriptor to become readable if no - data are immediately available on it. - - {{{Lwt}}} contains a scheduler which is responsible for managing - multiple operations waiting for events, and restarting them when needed. - This scheduler is implemented by the two modules {{{Lwt_engine}}} - and {{{Lwt_main}}}. {{{Lwt_engine}}} is a low-level module, it - provides a signature for custom I/O multiplexers as well as two built-in - implementations, {{{libev}}} and {{{select}}}. The signature is given by the - class {{{Lwt_engine.t}}}. - - {{{libev}}} is used by default on Linux, because it supports any - number of file descriptors, while {{{select}}} supports only 1024. {{{libev}}} - also much more efficient. On Windows, {{{Unix.select}}} is used because - {{{libev}}} does not work properly. The user may change the backend in use at - any time. - - If you see an {{{Invalid_argument}}} error on {{{Unix.select}}}, it - may be because the 1024 file descriptor limit was exceeded. Try - switching to {{{libev}}}, if possible. - - The engine can also be used directly in order to integrate other - libraries with {{{Lwt}}}. For example, {{{GTK}}} needs to be notified - when some events are received. If you use {{{Lwt}}} with {{{GTK}}} - you need to use the {{{Lwt}}} scheduler to monitor {{{GTK}}} - sources. This is what is done by the {{{Lwt_glib}}} library. - - The {{{Lwt_main}}} module contains the //main loop// of - {{{Lwt}}}. It is run by calling the function {{{Lwt_main.run}}}: - -< 'a ->> - - This function continuously runs the scheduler until the promise passed - as argument is resolved. - - To make sure {{{Lwt}}} is compiled with {{{libev}}} support, - tell opam that the library is available on the system by installing the - [[http://opam.ocaml.org/packages/conf-libev/conf-libev.4-11/|conf-libev]] - package. You may get the actual library with your system package manager: - - * {{{brew install libev}}} on MacOSX, - * {{{apt-get install libev-dev}}} on Debian/Ubuntu, or - * {{{yum install libev-devel}}} on CentOS, which requires to set - {{{export C_INCLUDE_PATH=/usr/include/libev/}}} and - {{{export LIBRARY_PATH=/usr/lib64/}}} before calling - {{{opam install conf-libev}}}. - - -=== Logging === - - For logging, we recommend the {{{logs}}} package from opam, which includes an - Lwt-aware module {{{Logs_lwt}}}. - -== The Lwt.react library == - - The {{{Lwt_react}}} module provides helpers for using the {{{react}}} - library with {{{Lwt}}}. It extends the {{{React}}} module by adding - {{{Lwt}}}-specific functions. It can be used as a replacement of - {{{React}}}. For example you can add at the beginning of your - program: - -<> - - instead of: - -<> - - or: - -<> - - Among the added functionalities we have {{{Lwt_react.E.next}}}, which - takes an event and returns a promise which will be pending until the next - occurrence of this event. For example: - -< -val push : '_a -> unit = -# let p = E.next event;; -val p : '_a Lwt.t = -# Lwt.state p;; -- : '_a Lwt.state = Lwt.Sleep -# push 42;; -- : unit = () -# Lwt.state p;; -- : int Lwt.state = Lwt.Return 42 ->> - - Another interesting feature is the ability to limit events - (resp. signals) from occurring (resp. changing) too often. For example, - suppose you are doing a program which displays something on the screen - each time a signal changes. If at some point the signal changes 1000 - times per second, you probably don't want to render it 1000 times per - second. For that you use {{{Lwt_react.S.limit}}}: - -< unit Lwt.t) -> 'a React.signal -> 'a React.signal ->> - - {{{Lwt_react.S.limit f signal}}} returns a signal which varies as - {{{signal}}} except that two consecutive updates are separated by a - call to {{{f}}}. For example if {{{f}}} returns a promise which is pending - for 0.1 seconds, then there will be no more than 10 changes per - second: - -< Lwt_unix.sleep 0.1) signal in - - (* Redraw the screen each time the limited signal change: *) - S.notify_p draw signal' ->> - -== Other libraries == - -=== Detaching computation to preemptive threads === - - It may happen that you want to run a function which will take time to - compute or that you want to use a blocking function that cannot be - used in a non-blocking way. For these situations, {{{Lwt}}} allows you to - //detach// the computation to a preemptive thread. - - This is done by the module {{{Lwt_preemptive}}} of the - {{{lwt.unix}}} package which maintains a pool of system - threads. The main function is: - -< 'b) -> 'a -> 'b Lwt.t ->> - - {{{detach f x}}} will execute {{{f x}}} in another thread and - return a pending promise, usable from the main thread, which will be fulfilled - with the result of the preemptive thread. - - If you want to trigger some {{{Lwt}}} operations from your detached thread, - you have to call back into the main thread using - {{{Lwt_preemptive.run_in_main}}}: - -< 'a Lwt.t) -> 'a ->> - - This is roughly the equivalent of {{{Lwt.main_run}}}, but for detached - threads, rather than for the whole process. Note that you must not call - {{{Lwt_main.run}}} in a detached thread. - -=== SSL support === - - The library {{{Lwt_ssl}}} allows use of SSL asynchronously. - -== Writing stubs using {{{Lwt}}} == - -=== Thread-safe notifications === - - If you want to notify the main thread from another thread, you can use the {{{Lwt}}} - thread safe notification system. First you need to create a notification identifier - (which is just an integer) from the OCaml side using the - {{{Lwt_unix.make_notification}}} function, then you can send it from either the - OCaml code with {{{Lwt_unix.send_notification}}} function, or from the C code using - the function {{{lwt_unix_send_notification}}} (defined in {{{lwt_unix_.h}}}). - - Notifications are received and processed asynchronously by the main thread. - -=== Jobs === - - For operations that cannot be executed asynchronously, {{{Lwt}}} - uses a system of jobs that can be executed in a different threads. A - job is composed of three functions: - - * A stub function to create the job. It must allocate a new job - structure and fill its [worker] and [result] fields. This - function is executed in the main thread. - The return type for the OCaml external must be of the form {{{'a job}}}. - * A function which executes the job. This one may be executed asynchronously - in another thread. This function must not: - ** access or allocate OCaml block values (tuples, strings, ...), - ** call OCaml code. - * A function which reads the result of the job, frees resources and - returns the result as an OCaml value. This function is executed in - the main thread. - - With {{{Lwt < 2.3.3}}}, 4 functions (including 3 stubs) were - required. It is still possible to use this mode but it is - deprecated. - - We show as example the implementation of {{{Lwt_unix.mkdir}}}. On the C - side we have: - -<result = mkdir(job->path, job->mode); - /* Save the value of errno. */ - job->errno_copy = errno; -} - -/* The function building the caml result. */ -static value result_mkdir(struct job_mkdir* job) -{ - /* Check for errors. */ - if (job->result < 0) { - /* Save the value of errno so we can use it - once the job has been freed. */ - int error = job->errno_copy; - /* Copy the contents of job->path into a caml string. */ - value string_argument = caml_copy_string(job->path); - /* Free the job structure. */ - lwt_unix_free_job(&job->job); - /* Raise the error. */ - unix_error(error, "mkdir", string_argument); - } - /* Free the job structure. */ - lwt_unix_free_job(&job->job); - /* Return the result. */ - return Val_unit; -} - -/* The stub creating the job structure. */ -CAMLprim value lwt_unix_mkdir_job(value path, value mode) -{ - /* Get the length of the path parameter. */ - mlsize_t len_path = caml_string_length(path) + 1; - /* Allocate a new job. */ - struct job_mkdir* job = - (struct job_mkdir*)lwt_unix_new_plus(struct job_mkdir, len_path); - /* Set the offset of the path parameter inside the job structure. */ - job->path = job->data; - /* Copy the path parameter inside the job structure. */ - memcpy(job->path, String_val(path), len_path); - /* Initialize function fields. */ - job->job.worker = (lwt_unix_job_worker)worker_mkdir; - job->job.result = (lwt_unix_job_result)result_mkdir; - /* Copy the mode parameter. */ - job->mode = Int_val(mode); - /* Wrap the structure into a caml value. */ - return lwt_unix_alloc_job(&job->job); -} ->> - - and on the ocaml side: - -< int -> unit job = "lwt_unix_mkdir_job" - -(* The ocaml function. *) -let mkdir name perms = Lwt_unix.run_job (mkdir_job name perms) ->> diff --git a/dune-project b/dune-project index bfe19a2026..782590fc2b 100644 --- a/dune-project +++ b/dune-project @@ -1 +1,60 @@ -(lang dune 1.8) +(lang dune 1.12) + +(name lwt) + +(generate_opam_files true) + +(maintainers + "Raphaël Proust " + "Anton Bachin ") +(authors "Jérôme Vouillon" "Jérémie Dimino") +(license MIT) +(source (github ocsigen/lwt)) +(documentation "https://ocsigen.org/lwt") + +(package + (name lwt_ppx_let) + (synopsis "Dummy package context for ppx_let tests") + (description "Internal package used to partition ppx_let tests.") + (depends + (ocaml (>= 4.08)) + (ppx_let :with-test) + lwt)) + +(package + (name lwt_ppx) + (synopsis "PPX syntax for Lwt, providing something similar to async/await from JavaScript") + (depends + (ocaml (>= 4.08)) + (ppxlib (>= 0.16.0)) + lwt)) + +(package + (name lwt_react) + (synopsis "Helpers for using React with Lwt") + (depends + (ocaml (>= 4.08)) + (cppo (and :build (>= 1.1.0))) + (lwt (>= 3.0.0)) + (react (>= 1.0.0)))) + +(package + (name lwt) + (synopsis "Promises and event-driven I/O") + (description "A promise is a value that may become determined in the future. + +Lwt provides typed, composable promises. Promises that are resolved by I/O are +resolved by Lwt in parallel. + +Meanwhile, OCaml code, including code creating and waiting on promises, runs in +a single thread by default. This reduces the need for locks or other +synchronization primitives. Code can be run in parallel on an opt-in basis. +") + (depends + (ocaml (>= 4.08)) + (cppo (and :build (>= 1.1.0))) + (ocamlfind (and :dev (>= 1.7.3-1))) + (odoc (and :with-doc (>= 2.3.0))) + dune-configurator + ocplib-endian) + (depopts base-threads base-unix conf-libev)) diff --git a/esy.json b/esy.json index 9a2d6b95cb..ffa238271f 100644 --- a/esy.json +++ b/esy.json @@ -5,12 +5,10 @@ "dependencies": { "ocaml": "~4.9.0", "@opam/conf-libev": "*", - "@opam/bisect_ppx": "*", - "@opam/luv": "*" + "@opam/bisect_ppx": "*" }, "resolutions": { - "@opam/conf-libev": "esy-packages/libev:package.json#0b5eb66", - "@opam/luv": "aantron/luv:luv.opam#df02a9ac6e9e9fc6427e7853d260230fcb96bd2a" + "@opam/conf-libev": "esy-packages/libev:package.json#0b5eb66" } } } diff --git a/lwt.opam b/lwt.opam index 869f1776cc..2f0979c474 100644 --- a/lwt.opam +++ b/lwt.opam @@ -1,52 +1,56 @@ +# This file is generated by dune, edit dune-project instead opam-version: "2.0" - synopsis: "Promises and event-driven I/O" +description: """ +A promise is a value that may become determined in the future. + +Lwt provides typed, composable promises. Promises that are resolved by I/O are +resolved by Lwt in parallel. -version: "5.6.1" +Meanwhile, OCaml code, including code creating and waiting on promises, runs in +a single thread by default. This reduces the need for locks or other +synchronization primitives. Code can be run in parallel on an opt-in basis. +""" +maintainer: [ + "Raphaël Proust " "Anton Bachin " +] +authors: ["Jérôme Vouillon" "Jérémie Dimino"] license: "MIT" homepage: "https://github.com/ocsigen/lwt" doc: "https://ocsigen.org/lwt" bug-reports: "https://github.com/ocsigen/lwt/issues" - -authors: [ - "Jérôme Vouillon" - "Jérémie Dimino" -] -maintainer: [ - "Raphaël Proust " - "Anton Bachin " -] -dev-repo: "git+https://github.com/ocsigen/lwt.git" - depends: [ + "dune" {>= "1.12"} + "ocaml" {>= "4.08"} "cppo" {build & >= "1.1.0"} - "dune" {>= "1.8.0"} + "ocamlfind" {dev & >= "1.7.3-1"} + "odoc" {with-doc & >= "2.3.0"} "dune-configurator" - "ocaml" {>= "4.08"} "ocplib-endian" - - # Until https://github.com/aantron/bisect_ppx/pull/327. - # "bisect_ppx" {dev & >= "2.0.0"} - "ocamlfind" {dev & >= "1.7.3-1"} ] - -depopts: [ - "base-threads" - "base-unix" - "conf-libev" -] - +depopts: ["base-threads" "base-unix" "conf-libev"] +dev-repo: "git+https://github.com/ocsigen/lwt.git" build: [ - ["dune" "exec" "-p" name "src/unix/config/discover.exe" "--" "--save" - "--use-libev" "%{conf-libev:installed}%"] - ["dune" "build" "-p" name "-j" jobs] + ["dune" "subst"] {pinned} + [ + "dune" + "exec" + "-p" + name + "src/unix/config/discover.exe" + "--" + "--save" + "--use-libev" "%{conf-libev:installed}%" + ] + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] ] - -description: "A promise is a value that may become determined in the future. - -Lwt provides typed, composable promises. Promises that are resolved by I/O are -resolved by Lwt in parallel. - -Meanwhile, OCaml code, including code creating and waiting on promises, runs in -a single thread by default. This reduces the need for locks or other -synchronization primitives. Code can be run in parallel on an opt-in basis." diff --git a/lwt.opam.template b/lwt.opam.template new file mode 100644 index 0000000000..8fc8771fc6 --- /dev/null +++ b/lwt.opam.template @@ -0,0 +1,24 @@ +build: [ + ["dune" "subst"] {pinned} + [ + "dune" + "exec" + "-p" + name + "src/unix/config/discover.exe" + "--" + "--save" + "--use-libev" "%{conf-libev:installed}%" + ] + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] diff --git a/lwt_luv.opam b/lwt_luv.opam deleted file mode 100644 index b22e0ee5ea..0000000000 --- a/lwt_luv.opam +++ /dev/null @@ -1,29 +0,0 @@ -opam-version: "2.0" - -synopsis: "Libuv engine for Lwt" - -version: "1.0.0" -license: "MIT" -homepage: "https://github.com/ocsigen/lwt" -doc: "https://ocsigen.org/lwt" -bug-reports: "https://github.com/ocsigen/lwt/issues" - -maintainer: [ - "Anton Bachin " - "Ulrik Strid " -] -dev-repo: "git+https://github.com/ocsigen/lwt.git" - -depends: [ - "lwt" - "dune" {>= "1.8.0"} - "ocaml" {>= "4.08"} - "luv" - - # Until https://github.com/aantron/bisect_ppx/pull/327. - # "bisect_ppx" {dev & >= "2.0.0"} -] - -build: [ - ["dune" "build" "-p" name "-j" jobs] -] diff --git a/lwt_ppx.opam b/lwt_ppx.opam index 63c37abe81..958244c1f9 100644 --- a/lwt_ppx.opam +++ b/lwt_ppx.opam @@ -1,28 +1,33 @@ +# This file is generated by dune, edit dune-project instead opam-version: "2.0" - -synopsis: "PPX syntax for Lwt, providing something similar to async/await from JavaScript" - -version: "2.1.0" +synopsis: + "PPX syntax for Lwt, providing something similar to async/await from JavaScript" +maintainer: [ + "Raphaël Proust " "Anton Bachin " +] +authors: ["Jérôme Vouillon" "Jérémie Dimino"] license: "MIT" homepage: "https://github.com/ocsigen/lwt" -doc: "https://ocsigen.org/lwt/dev/api/Ppx_lwt" +doc: "https://ocsigen.org/lwt" bug-reports: "https://github.com/ocsigen/lwt/issues" - -authors: [ - "Gabriel Radanne" -] -maintainer: [ - "Anton Bachin " -] -dev-repo: "git+https://github.com/ocsigen/lwt.git" - depends: [ - "dune" {>= "1.8.0"} - "lwt" + "dune" {>= "1.12"} "ocaml" {>= "4.08"} "ppxlib" {>= "0.16.0"} + "lwt" ] - build: [ - ["dune" "build" "-p" name "-j" jobs] + ["dune" "subst"] {pinned} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] ] +dev-repo: "git+https://github.com/ocsigen/lwt.git" diff --git a/lwt_ppx_let.opam b/lwt_ppx_let.opam index d2114baa8a..8fbb382a3d 100644 --- a/lwt_ppx_let.opam +++ b/lwt_ppx_let.opam @@ -1,28 +1,33 @@ +# This file is generated by dune, edit dune-project instead opam-version: "2.0" - synopsis: "Dummy package context for ppx_let tests" - -version: "5.6.0" +description: "Internal package used to partition ppx_let tests." +maintainer: [ + "Raphaël Proust " "Anton Bachin " +] +authors: ["Jérôme Vouillon" "Jérémie Dimino"] license: "MIT" homepage: "https://github.com/ocsigen/lwt" doc: "https://ocsigen.org/lwt" bug-reports: "https://github.com/ocsigen/lwt/issues" - -authors: [ - "Jérôme Vouillon" - "Jérémie Dimino" -] -maintainer: [ - "Raphaël Proust " - "Anton Bachin " -] -dev-repo: "git+https://github.com/ocsigen/lwt.git" - depends: [ - "dune" {>= "1.8.0"} - "lwt" - "ppx_let" {with-test} + "dune" {>= "1.12"} "ocaml" {>= "4.08"} + "ppx_let" {with-test} + "lwt" ] - -description: "Internal package used to partition ppx_let tests." +build: [ + ["dune" "subst"] {pinned} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] +dev-repo: "git+https://github.com/ocsigen/lwt.git" diff --git a/lwt_react.opam b/lwt_react.opam index c1623389d1..7f5d7e1957 100644 --- a/lwt_react.opam +++ b/lwt_react.opam @@ -1,29 +1,33 @@ +# This file is generated by dune, edit dune-project instead opam-version: "2.0" - synopsis: "Helpers for using React with Lwt" - -version: "1.2.0" +maintainer: [ + "Raphaël Proust " "Anton Bachin " +] +authors: ["Jérôme Vouillon" "Jérémie Dimino"] license: "MIT" homepage: "https://github.com/ocsigen/lwt" -doc: "https://ocsigen.org/lwt/dev/api/Lwt_react" +doc: "https://ocsigen.org/lwt" bug-reports: "https://github.com/ocsigen/lwt/issues" - -authors: [ - "Jérémie Dimino" -] -maintainer: [ - "Anton Bachin " -] -dev-repo: "git+https://github.com/ocsigen/lwt.git" - depends: [ - "dune" {>= "1.8.0"} - "lwt" {>= "3.0.0"} + "dune" {>= "1.12"} "ocaml" {>= "4.08"} - "react" {>= "1.0.0"} "cppo" {build & >= "1.1.0"} + "lwt" {>= "3.0.0"} + "react" {>= "1.0.0"} ] - build: [ - ["dune" "build" "-p" name "-j" jobs] + ["dune" "subst"] {pinned} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] ] +dev-repo: "git+https://github.com/ocsigen/lwt.git" diff --git a/src/core/index.mld b/src/core/index.mld index 238f380f19..603f259256 100644 --- a/src/core/index.mld +++ b/src/core/index.mld @@ -79,7 +79,7 @@ In Lwt, {1 Additional Docs} -- {{:http://ocsigen.org/lwt/} Online manual}. +- {{!page-manual} Manual} ({{:http://ocsigen.org/lwt/} Online manual}). - {{:https://github.com/dkim/rwo-lwt#readme} Concurrent Programming with Lwt} is a nice source of Lwt examples. They are translations of code from Real World OCaml, but are just as useful if you are not reading the book. diff --git a/src/core/lwt.ml b/src/core/lwt.ml index cfab662466..be6f5128fb 100644 --- a/src/core/lwt.ml +++ b/src/core/lwt.ml @@ -710,7 +710,20 @@ struct end open Basic_helpers - +(* Small helpers to avoid catching ocaml-runtime exceptions *) +module Exception_filter = struct + type t = exn -> bool + let handle_all = fun _ -> true + let handle_all_except_runtime = function + | Out_of_memory -> false + | Stack_overflow -> false + | _ -> true + let v = + (* Default value: the legacy behaviour to avoid breaking programs *) + ref handle_all + let set f = v := f + let run e = !v e +end module Sequence_associated_storage : sig @@ -791,7 +804,7 @@ struct let result = f () in current_storage := saved_storage; result - with exn -> + with exn when Exception_filter.run exn -> current_storage := saved_storage; raise exn end @@ -1129,7 +1142,8 @@ struct be reject later, it is not the responsibility of this function to pass the exception to [!async_exception_hook]. *) try f v - with exn -> !async_exception_hook exn + with exn when Exception_filter.run exn -> + !async_exception_hook exn @@ -1826,7 +1840,10 @@ struct | Fulfilled v -> current_storage := saved_storage; - let p' = try f v with exn -> fail exn in + let p' = + try f v with exn + when Exception_filter.run exn -> fail exn + in let Internal p' = to_internal_promise p' in (* Run the user's function [f]. *) @@ -1889,7 +1906,10 @@ struct | Fulfilled v -> current_storage := saved_storage; - let p' = try f v with exn -> fail (add_loc exn) in + let p' = + try f v + with exn when Exception_filter.run exn -> + fail (add_loc exn) in let Internal p' = to_internal_promise p' in let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in @@ -1943,7 +1963,10 @@ struct | Fulfilled v -> current_storage := saved_storage; - let p''_result = try Fulfilled (f v) with exn -> Rejected exn in + let p''_result = + try Fulfilled (f v) with exn + when Exception_filter.run exn -> Rejected exn + in let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in let p'' = underlying p'' in @@ -1970,7 +1993,9 @@ struct ~run_immediately_and_ensure_tail_call:true ~callback:(fun () -> to_public_promise - {state = try Fulfilled (f v) with exn -> Rejected exn}) + {state = + try Fulfilled (f v) + with exn when Exception_filter.run exn -> Rejected exn}) ~if_deferred:(fun () -> let (p'', callback) = create_result_promise_and_callback_if_deferred () in @@ -1987,7 +2012,10 @@ struct external reraise : exn -> 'a = "%reraise" let catch f h = - let p = try f () with exn -> fail exn in + let p = + try f () + with exn when Exception_filter.run exn -> fail exn + in let Internal p = to_internal_promise p in let p = underlying p in @@ -2009,7 +2037,10 @@ struct | Rejected exn -> current_storage := saved_storage; - let p' = try h exn with exn -> fail exn in + let p' = + try h exn + with exn when Exception_filter.run exn -> fail exn + in let Internal p' = to_internal_promise p' in let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in @@ -2042,7 +2073,10 @@ struct p'' let backtrace_catch add_loc f h = - let p = try f () with exn -> fail exn in + let p = + try f () + with exn when Exception_filter.run exn -> fail exn + in let Internal p = to_internal_promise p in let p = underlying p in @@ -2064,7 +2098,11 @@ struct | Rejected exn -> current_storage := saved_storage; - let p' = try h exn with exn -> fail (add_loc exn) in + let p' = + try h exn + with exn when Exception_filter.run exn -> + fail (add_loc exn) + in let Internal p' = to_internal_promise p' in let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in @@ -2097,7 +2135,10 @@ struct p'' let try_bind f f' h = - let p = try f () with exn -> fail exn in + let p = + try f () + with exn when Exception_filter.run exn -> fail exn + in let Internal p = to_internal_promise p in let p = underlying p in @@ -2111,7 +2152,10 @@ struct | Fulfilled v -> current_storage := saved_storage; - let p' = try f' v with exn -> fail exn in + let p' = + try f' v + with exn when Exception_filter.run exn -> fail exn + in let Internal p' = to_internal_promise p' in let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in @@ -2124,7 +2168,10 @@ struct | Rejected exn -> current_storage := saved_storage; - let p' = try h exn with exn -> fail exn in + let p' = + try h exn + with exn when Exception_filter.run exn -> fail exn + in let Internal p' = to_internal_promise p' in let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in @@ -2163,7 +2210,10 @@ struct p'' let backtrace_try_bind add_loc f f' h = - let p = try f () with exn -> fail exn in + let p = + try f () + with exn when Exception_filter.run exn -> fail exn + in let Internal p = to_internal_promise p in let p = underlying p in @@ -2177,7 +2227,11 @@ struct | Fulfilled v -> current_storage := saved_storage; - let p' = try f' v with exn -> fail (add_loc exn) in + let p' = + try f' v + with exn when Exception_filter.run exn -> + fail (add_loc exn) + in let Internal p' = to_internal_promise p' in let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in @@ -2190,7 +2244,11 @@ struct | Rejected exn -> current_storage := saved_storage; - let p' = try h exn with exn -> fail (add_loc exn) in + let p' = + try h exn + with exn when Exception_filter.run exn -> + fail (add_loc exn) + in let Internal p' = to_internal_promise p' in let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in @@ -2441,7 +2499,10 @@ struct external reraise : exn -> 'a = "%reraise" let dont_wait f h = - let p = try f () with exn -> fail exn in + let p = + try f () + with exn when Exception_filter.run exn -> fail exn + in let Internal p = to_internal_promise p in match (underlying p).state with @@ -2461,7 +2522,10 @@ struct add_implicitly_removed_callback p_callbacks callback let async f = - let p = try f () with exn -> fail exn in + let p = + try f () + with exn when Exception_filter.run exn -> fail exn + in let Internal p = to_internal_promise p in match (underlying p).state with @@ -3062,37 +3126,40 @@ struct - let apply f x = try f x with exn -> fail exn + let apply f x = + try f x with exn when Exception_filter.run exn -> fail exn - let wrap f = try return (f ()) with exn -> fail exn + let wrap f = + try return (f ()) + with exn when Exception_filter.run exn -> fail exn let wrap1 f x1 = try return (f x1) - with exn -> fail exn + with exn when Exception_filter.run exn -> fail exn let wrap2 f x1 x2 = try return (f x1 x2) - with exn -> fail exn + with exn when Exception_filter.run exn -> fail exn let wrap3 f x1 x2 x3 = try return (f x1 x2 x3) - with exn -> fail exn + with exn when Exception_filter.run exn -> fail exn let wrap4 f x1 x2 x3 x4 = try return (f x1 x2 x3 x4) - with exn -> fail exn + with exn when Exception_filter.run exn -> fail exn let wrap5 f x1 x2 x3 x4 x5 = try return (f x1 x2 x3 x4 x5) - with exn -> fail exn + with exn when Exception_filter.run exn -> fail exn let wrap6 f x1 x2 x3 x4 x5 x6 = try return (f x1 x2 x3 x4 x5 x6) - with exn -> fail exn + with exn when Exception_filter.run exn -> fail exn let wrap7 f x1 x2 x3 x4 x5 x6 x7 = try return (f x1 x2 x3 x4 x5 x6 x7) - with exn -> fail exn + with exn when Exception_filter.run exn -> fail exn diff --git a/src/core/lwt.mli b/src/core/lwt.mli index 4d8ad6dd60..552c536b58 100644 --- a/src/core/lwt.mli +++ b/src/core/lwt.mli @@ -1999,6 +1999,49 @@ val ignore_result : _ t -> unit resolved, completing any associated side effects along the way. In fact, the function that does {e that} is ordinary {!Lwt.bind}. *) +(** {4 Runtime exception filters} + + Depending on the kind of programs that you write, you may need to treat + exceptions thrown by the OCaml runtime (namely [Out_of_memory] and + [Stack_overflow]) differently than all the other exceptions. This is because + (a) these exceptions are not reproducible (in that they are thrown at + different points of your program depending on the machine that your program + runs on) and (b) recovering from these errors may be impossible. + + The helpers below allow you to change the way that Lwt handles the two OCaml + runtime exceptions [Out_of_memory] and [Stack_overflow]. *) + +module Exception_filter: sig + + (** An [Exception_filter.t] is a value which indicates to Lwt what exceptions to + catch and what exceptions to let bubble up all the way out of the main loop + immediately. *) + type t + + (** [handle_all] is the default filter. With it the all the exceptions + (including [Out_of_memory] and [Stack_overflow]) can be handled: caught + and transformed into rejected promises. *) + val handle_all : t + + (** [handle_all_except_runtime] is a filter which lets the OCaml runtime + exceptions ([Out_of_memory] and [Stack_overflow]) go through all the Lwt + abstractions and bubble all the way out of the call to [Lwt_main.run]. + + Note that if you set this handler, then the runtime exceptions leave the + Lwt internal state inconsistent. For this reason, you will not be able to + call [Lwt_main.run] again after such an exception has escaped + [Lwt_main.run]. *) + val handle_all_except_runtime : t + + (** [set] sets the given exception filter globally. You should call this + function at most once during the start of your program, before the + first call to [Lwt_main.run]. *) + val set : t -> unit + + (**/**) + val run : exn -> bool + +end (**/**) diff --git a/src/core/lwt_pool.ml b/src/core/lwt_pool.ml index 1605bc89fa..bf06787ab7 100644 --- a/src/core/lwt_pool.ml +++ b/src/core/lwt_pool.ml @@ -108,7 +108,7 @@ let validate_and_return p c = resolver is waiting. *) dispose p c >>= fun () -> replace_disposed p; - Lwt.fail e) + Lwt.reraise e) (* Acquire a pool member. *) let acquire p = diff --git a/src/core/lwt_seq.ml b/src/core/lwt_seq.ml index 5c9fe3ade6..dd3f0396f2 100644 --- a/src/core/lwt_seq.ml +++ b/src/core/lwt_seq.ml @@ -270,7 +270,7 @@ let rec unfold f u () = match f u with | None -> return_nil | Some (x, u') -> Lwt.return (Cons (x, unfold f u')) - | exception exc -> Lwt.fail exc + | exception exc when Lwt.Exception_filter.run exc -> Lwt.reraise exc let rec unfold_lwt f u () = let* x = f u in @@ -283,29 +283,23 @@ let unfold_lwt f u () = | None -> return_nil | Some (x, u') -> Lwt.return (Cons (x, unfold_lwt f u')) -let rec of_list = function - | [] -> empty - | h :: t -> cons h (of_list t) +let rec of_list l () = + Lwt.return (match l with [] -> Nil | h :: t -> Cons (h, of_list t)) -let rec to_list seq = - seq () >>= function - | Nil -> Lwt.return_nil - | Cons (x, next) -> - let+ l = to_list next in - x :: l -let to_list seq = - Lwt.apply seq () >>= function - | Nil -> Lwt.return_nil - | Cons (x, next) -> - let+ l = to_list next in - x :: l +let to_list (seq : 'a t) = + let rec aux f seq = + Lwt.bind (seq ()) (function + | Nil -> Lwt.return (f []) + | Cons (h, t) -> aux (fun x -> f (h :: x)) t) + in + aux (fun x -> x) (Lwt.apply seq) let rec of_seq seq () = match seq () with | Seq.Nil -> return_nil | Seq.Cons (x, next) -> Lwt.return (Cons (x, (of_seq next))) - | exception exn -> Lwt.fail exn + | exception exn when Lwt.Exception_filter.run exn -> Lwt.reraise exn let rec of_seq_lwt (seq: 'a Lwt.t Seq.t): 'a t = fun () -> match seq () with @@ -321,4 +315,4 @@ let of_seq_lwt (seq: 'a Lwt.t Seq.t): 'a t = fun () -> let+ x = x in let next = of_seq_lwt next in Cons (x, next) - | exception exc -> Lwt.fail exc + | exception exc when Lwt.Exception_filter.run exc -> Lwt.reraise exc diff --git a/src/core/lwt_stream.ml b/src/core/lwt_stream.ml index 88e83f449d..cb75394058 100644 --- a/src/core/lwt_stream.ml +++ b/src/core/lwt_stream.ml @@ -279,9 +279,9 @@ class ['a] bounded_push_impl (info : 'a push_bounded) wakener_cell last close = let waiter, wakener = Lwt.task () in info.pushb_push_waiter <- waiter; info.pushb_push_wakener <- wakener; - Lwt.fail exn + Lwt.reraise exn | _ -> - Lwt.fail exn) + Lwt.reraise exn) end else begin (* Push the element at the end of the queue. *) enqueue' (Some x) last; @@ -367,11 +367,18 @@ let feed s = else begin (* Otherwise request a new element. *) let thread = - from.from_create () >>= fun x -> - (* Push the element to the end of the queue. *) - enqueue x s; - if x = None then Lwt.wakeup s.close (); - Lwt.return_unit + (* The function [from_create] can raise an exception (with + [raise], rather than returning a failed promise with + [Lwt.fail]). In this case, we have to catch the exception + and turn it into a safe failed promise. *) + Lwt.catch + (fun () -> + from.from_create () >>= fun x -> + (* Push the element to the end of the queue. *) + enqueue x s; + if x = None then Lwt.wakeup s.close (); + Lwt.return_unit) + Lwt.reraise in (* Allow other threads to access this thread. *) from.from_thread <- thread; @@ -1070,7 +1077,7 @@ let parse s f = (fun () -> f s) (fun exn -> s.node <- node; - Lwt.fail exn) + Lwt.reraise exn) let hexdump stream = let buf = Buffer.create 80 and num = ref 0 in diff --git a/src/core/lwt_stream.mli b/src/core/lwt_stream.mli index 11e9a84727..4f74483cf7 100644 --- a/src/core/lwt_stream.mli +++ b/src/core/lwt_stream.mli @@ -84,17 +84,17 @@ class type ['a] bounded_push = object is already blocked on [push], it raises {!Lwt_stream.Full}. *) method close : unit - (** Closes the stream. Any thread currently blocked on - {!Lwt_stream.bounded_push.push} fails with {!Lwt_stream.Closed}. *) + (** Closes the stream. Any thread currently blocked on a call to + the [push] method fails with {!Lwt_stream.Closed}. *) method count : int (** Number of elements in the stream queue. *) method blocked : bool - (** Is a thread is blocked on {!Lwt_stream.bounded_push.push} ? *) + (** Is a thread is blocked on a call to the [push] method? *) method closed : bool - (** Is the stream closed ? *) + (** Is the stream closed? *) method set_reference : 'a. 'a -> unit (** Set the reference to an external source. *) diff --git a/src/ppx/ppx_lwt.ml b/src/ppx/ppx_lwt.ml index c2030be231..e77486c1aa 100644 --- a/src/ppx/ppx_lwt.ml +++ b/src/ppx/ppx_lwt.ml @@ -23,7 +23,7 @@ let add_wildcard_case cases = if not has_wildcard then cases @ (let loc = Location.none in - [case ~lhs:[%pat? exn] ~guard:None ~rhs:[%expr Lwt.fail exn]]) + [case ~lhs:[%pat? exn] ~guard:None ~rhs:[%expr Lwt.reraise exn]]) else cases (** {3 Internal names} *) @@ -154,11 +154,11 @@ let lwt_expression mapper exp attributes ext_loc = Some (mapper#expression { new_exp with pexp_attributes }) (* [assert%lwt $e$] ≡ - [try Lwt.return (assert $e$) with exn -> Lwt.fail exn] *) + [try Lwt.return (assert $e$) with exn -> Lwt.reraise exn] *) | Pexp_assert e -> let new_exp = let loc = !default_loc in - [%expr try Lwt.return (assert [%e e]) with exn -> Lwt.fail exn] + [%expr try Lwt.return (assert [%e e]) with exn -> Lwt.reraise exn] in Some (mapper#expression { new_exp with pexp_attributes }) diff --git a/src/ppx/ppx_lwt.mli b/src/ppx/ppx_lwt.mli index 1d3a541e6a..0912c07e01 100644 --- a/src/ppx/ppx_lwt.mli +++ b/src/ppx/ppx_lwt.mli @@ -80,10 +80,10 @@ catch (fun () -> f x) prerr_endline msg; return () | exn -> - Lwt.fail exn) + Lwt.reraise exn) ]} - Note that the [exn -> Lwt.fail exn] branch is automatically added + Note that the [exn -> Lwt.reraise exn] branch is automatically added when needed. - finalizer: diff --git a/src/react/lwt_react.ml b/src/react/lwt_react.ml index 84b86c806c..8d141065ca 100644 --- a/src/react/lwt_react.ml +++ b/src/react/lwt_react.ml @@ -95,7 +95,12 @@ module E = struct let event, push = create () in let t = Lwt.pause () >>= fun () -> - Lwt_stream.iter (fun v -> try push v with exn -> !Lwt.async_exception_hook exn) stream in + Lwt_stream.iter + (fun v -> + try push v + with exn when Lwt.Exception_filter.run exn -> + !Lwt.async_exception_hook exn) + stream in with_finaliser (cancel_thread t) event let delay thread = diff --git a/src/unix/config/discover.ml b/src/unix/config/discover.ml index bd528cad7d..e7b81fcf68 100644 --- a/src/unix/config/discover.ml +++ b/src/unix/config/discover.ml @@ -165,7 +165,7 @@ sig val add_link_flags : string list -> unit end = struct - let c_flags = ref [] + let c_flags = ref ["-Wall"; "-fdiagnostics-color=always"] let link_flags = ref [] let extend c_flags' link_flags' = @@ -424,7 +424,7 @@ struct let code = {| #include - int main() + int main(void) { ev_default_loop(0); return 0; @@ -452,7 +452,7 @@ struct let code = {| #include - int main() + int main(void) { pthread_create(0, 0, 0, 0); return 0; @@ -494,7 +494,7 @@ struct compiles context {| #include - int main() + int main(void) { eventfd(0, 0); return 0; @@ -511,7 +511,7 @@ struct #include #include - int main() + int main(void) { struct msghdr msg; msg.msg_controllen = 0; @@ -531,7 +531,7 @@ struct #define _GNU_SOURCE #include - int main() + int main(void) { sched_getcpu(); return 0; @@ -549,7 +549,7 @@ struct #define _GNU_SOURCE #include - int main() + int main(void) { sched_getaffinity(0, 0, 0); return 0; @@ -562,7 +562,7 @@ struct #include #include - int main() + int main(void) { struct |} ^ struct_name ^ {| cred; socklen_t cred_len = sizeof(cred); @@ -612,7 +612,7 @@ struct #include #include - int main() + int main(void) { uid_t euid; gid_t egid; @@ -630,7 +630,7 @@ struct compiles context {| #include - int main() + int main(void) { int (*fdatasyncp)(int) = fdatasync; fdatasyncp(0); @@ -650,11 +650,10 @@ struct #include #include - int main() + int main(void) { - struct hostent *he; - struct servent *se; - he = + int x; + x = gethostbyname_r( (const char*)NULL, (struct hostent*)NULL, @@ -662,9 +661,9 @@ struct (int)0, (struct hostent**)NULL, (int*)NULL); - he = + x = gethostbyaddr_r( - (const char*)NULL, + (const void*)NULL, (int)0, (int)0, (struct hostent*)NULL, @@ -672,7 +671,7 @@ struct (int)0, (struct hostent**)NULL, (int*)NULL); - se = + x = getservbyname_r( (const char*)NULL, (const char*)NULL, @@ -680,7 +679,7 @@ struct (char*)NULL, (int)0, (struct servent**)NULL); - se = + x = getservbyport_r( (int)0, (const char*)NULL, @@ -688,20 +687,20 @@ struct (char*)NULL, (int)0, (struct servent**)NULL); - pr = + x = getprotoent_r( (struct protoent*)NULL, (char*)NULL, (int)0, (struct protoent**)NULL); - pr = + x = getprotobyname_r( (const char*)NULL, (struct protoent*)NULL, (char*)NULL, (int)0, (struct protoent**)NULL); - pr = + x = getprotobynumber_r( (int)0, (struct protoent*)NULL, @@ -734,7 +733,7 @@ struct #define NON_R_GETHOSTBYNAME 1 #endif - int main() + int main(void) { #if defined(NON_R_GETHOSTBYNAME) || defined(NON_R_GETHOSTBYNAME) #error "not available" @@ -751,7 +750,7 @@ struct #include #include - int main() { + int main(void) { struct stat *buf; double a, m, c; a = (double)buf->st_a|} ^ projection ^ {|; @@ -791,7 +790,7 @@ struct #include #include - int main() + int main(void) { int (*mincore_ptr)(const void*, size_t, char*) = mincore; return (int)(mincore_ptr == NULL); @@ -809,7 +808,7 @@ struct #include #include - int main() + int main(void) { accept4(0, NULL, 0, 0); return 0; diff --git a/src/unix/luv/dune b/src/unix/luv/dune deleted file mode 100644 index 20be1e689b..0000000000 --- a/src/unix/luv/dune +++ /dev/null @@ -1,16 +0,0 @@ -(* -*- tuareg -*- *) - -let preprocess = - match Sys.getenv "BISECT_ENABLE" with - | "yes" -> "(preprocess (pps bisect_ppx))" - | _ -> "" - | exception _ -> "" - -let () = Jbuild_plugin.V1.send @@ {| - -(library - (public_name lwt_luv) - (libraries luv lwt lwt.unix) - |} ^ preprocess ^ {| -) -|} diff --git a/src/unix/luv/lwt_luv.ml b/src/unix/luv/lwt_luv.ml deleted file mode 100644 index ae1f7ff656..0000000000 --- a/src/unix/luv/lwt_luv.ml +++ /dev/null @@ -1,57 +0,0 @@ -let from_unix : Unix.file_descr -> int = Obj.magic - -class engine = object - inherit Lwt_engine.abstract - - val loop = ref (Luv.Loop.default ()) - - method! fork = - Luv.Loop.fork !loop |> function - | Ok () -> () - | Error e -> failwith (Printf.sprintf "Could not handle the fork, this is probably a error in Lwt, please open a issue on the repo. \nError message: %s" (Luv.Error.err_name e)) - - method private cleanup = Luv.Loop.stop !loop - - method iter block = - match (block) with - | true -> Luv.Loop.run ~loop:!loop ~mode:`ONCE () |> ignore - | false -> Luv.Loop.run ~loop:!loop ~mode:`NOWAIT () |> ignore - - method private register_readable fd f = - let p = Luv.Poll.init ~loop:!loop (from_unix fd) in - match p with - | Ok poll -> - let () = Luv.Poll.start poll [`READABLE;] (fun _ -> f ()) in - lazy( - Luv.Poll.stop poll |> function - | Ok () -> () - | Error e -> failwith (Printf.sprintf "Could not stop read polling, this is probably a error in Lwt, please open a issue on the repo. \nError message: %s" (Luv.Error.err_name e)) - ) - | Result.Error e -> failwith (Printf.sprintf "Could not register fd for read polling, this is probably a error in Lwt, please open a issue on the repo. \nError message: %s" (Luv.Error.err_name e)) - - method private register_writable fd f = - let p = Luv.Poll.init ~loop:!loop (from_unix fd) in - match p with - | Ok poll -> - let () = Luv.Poll.start poll [`WRITABLE;] (fun _ -> f ()) in - lazy( - Luv.Poll.stop poll |> function - | Ok () -> () - | Error e -> failwith (Printf.sprintf "Could not stop write polling, this is probably a error in Lwt, please open a issue on the repo. \nError message: %s" (Luv.Error.err_name e)) - ) - | Result.Error e -> failwith (Printf.sprintf "Could not register fd for write polling, this is probably a error in Lwt, please open a issue on the repo. \nError message: %s" (Luv.Error.err_name e)) - - method private register_timer delay repeat f = - let delay_ms = (int_of_float (delay *. 1000.)) in - let t = Luv.Timer.init ~loop:!loop () in - match t with - | Result.Error e -> failwith (Printf.sprintf "Could not initialize a timer, this is probably a error in Lwt, please open a issue on the repo. \nError message: %s" (Luv.Error.err_name e)) - | Ok timer -> - let timer_fn = match repeat with - | true -> Luv.Timer.start ~repeat:delay_ms timer - | false -> Luv.Timer.start timer - in - match timer_fn delay_ms f with - | Ok () -> lazy(Luv.Timer.stop timer |> ignore) - | Result.Error e -> failwith (Printf.sprintf "Could not start a timer, this is probably a error in Lwt, please open a issue on the repo. \nError message: %s" (Luv.Error.err_name e)) -end diff --git a/src/unix/luv/lwt_luv.mli b/src/unix/luv/lwt_luv.mli deleted file mode 100644 index fccd982b9a..0000000000 --- a/src/unix/luv/lwt_luv.mli +++ /dev/null @@ -1,2 +0,0 @@ -(** Engine based on libuv. *) -class engine : Lwt_engine.t diff --git a/src/unix/lwt_engine.ml b/src/unix/lwt_engine.ml index ad9bd334db..22634712aa 100644 --- a/src/unix/lwt_engine.ml +++ b/src/unix/lwt_engine.ml @@ -108,6 +108,8 @@ class virtual abstract = object(self) method timer_count = Lwt_sequence.length timers method fork = () + + method forwards_signal (_signum:int) = false end class type t = object @@ -438,6 +440,7 @@ let readable_count () = !current#readable_count let writable_count () = !current#writable_count let timer_count () = !current#timer_count let fork () = !current#fork +let forwards_signal n = !current#forwards_signal n module Versioned = struct diff --git a/src/unix/lwt_engine.mli b/src/unix/lwt_engine.mli index 3c5ea31ee5..d353715574 100644 --- a/src/unix/lwt_engine.mli +++ b/src/unix/lwt_engine.mli @@ -53,6 +53,13 @@ val fake_io : Unix.file_descr -> unit val fork : unit -> unit (** Called internally by Lwt_unix.fork to make sure we don't get strange behaviour *) +val forwards_signal : int -> bool +(** [forwards_signal signum] is [true] if the engine will call {!Lwt_unix.handle_signal} + when signal [signum] occurs. In this case, Lwt will not install its own signal handler. + + Normally, this just returns [false], but when Lwt is used in combination + with other IO libraries, this allows sharing e.g. the SIGCHLD handler. *) + (** {2 Engines} *) (** An engine represents a set of functions used to register different @@ -82,6 +89,7 @@ class virtual abstract : object method readable_count : int method writable_count : int method timer_count : int + method forwards_signal : int -> bool (** {2 Backend methods} *) diff --git a/src/unix/lwt_io.ml b/src/unix/lwt_io.ml index 1e9cca05e2..8b3033faec 100644 --- a/src/unix/lwt_io.ml +++ b/src/unix/lwt_io.ml @@ -231,7 +231,7 @@ let perform_io : type mode. mode _channel -> int Lwt.t = fun ch -> (function | Unix.Unix_error (Unix.EPIPE, _, _) -> Lwt.return 0 - | exn -> Lwt.fail exn) [@ocaml.warning "-4"] + | exn -> Lwt.reraise exn) [@ocaml.warning "-4"] else perform ch.buffer ptr len in @@ -525,7 +525,7 @@ let make : max = (match mode with | Input -> 0 | Output -> size); - close = lazy(Lwt.catch close Lwt.fail); + close = lazy(Lwt.catch close Lwt.reraise); abort_waiter = abort_waiter; abort_wakener = abort_wakener; main = wrapper; @@ -533,8 +533,12 @@ let make : mode = mode; offset = 0L; typ = - Type_normal - (perform_io, fun pos cmd -> try seek pos cmd with e -> Lwt.fail e); + Type_normal ( + perform_io, + fun pos cmd -> + try seek pos cmd + with e when Lwt.Exception_filter.run e -> Lwt.reraise e + ); } and wrapper = { state = Idle; channel = ch; @@ -674,7 +678,7 @@ struct let ptr = ic.ptr in if ptr = ic.max then refill ic >>= function - | 0 -> Lwt.fail End_of_file + | 0 -> raise End_of_file | _ -> read_char ic else begin ic.ptr <- ptr + 1; @@ -686,7 +690,7 @@ struct (fun () -> read_char ic >|= fun ch -> Some ch) (function | End_of_file -> Lwt.return_none - | exn -> Lwt.fail exn) + | exn -> Lwt.reraise exn) let read_line ic = let buf = Buffer.create 128 in @@ -707,7 +711,7 @@ struct if cr_read then Buffer.add_char buf '\r'; Lwt.return(Buffer.contents buf) | exn -> - Lwt.fail exn) + Lwt.reraise exn) in read_char ic >>= function | '\r' -> loop true @@ -719,7 +723,7 @@ struct (fun () -> read_line ic >|= fun ch -> Some ch) (function | End_of_file -> Lwt.return_none - | exn -> Lwt.fail exn) + | exn -> Lwt.reraise exn) let unsafe_read_into' ic blit buf ofs len = let avail = ic.max - ic.ptr in @@ -767,7 +771,7 @@ struct let rec loop ic buf ofs len = read_into ic buf ofs len >>= function | 0 -> - Lwt.fail End_of_file + raise End_of_file | n -> let len = len - n in if len = 0 then @@ -842,12 +846,12 @@ struct Lwt.return (Bytes.unsafe_to_string buf) let read_value ic = - let header = Bytes.create 20 in - unsafe_read_into_exactly ic header 0 20 >>= fun () -> + let header = Bytes.create Marshal.header_size in + unsafe_read_into_exactly ic header 0 Marshal.header_size >>= fun () -> let bsize = Marshal.data_size header 0 in - let buffer = Bytes.create (20 + bsize) in - Bytes.unsafe_blit header 0 buffer 0 20; - unsafe_read_into_exactly ic buffer 20 bsize >>= fun () -> + let buffer = Bytes.create (Marshal.header_size + bsize) in + Bytes.unsafe_blit header 0 buffer 0 Marshal.header_size; + unsafe_read_into_exactly ic buffer Marshal.header_size bsize >>= fun () -> Lwt.return (Marshal.from_bytes buffer 0) (* +---------------------------------------------------------------+ @@ -981,7 +985,7 @@ struct if ic.max - ic.ptr < size then refill ic >>= function | 0 -> - Lwt.fail End_of_file + raise End_of_file | _ -> read_block_unsafe ic size f else begin @@ -1436,7 +1440,7 @@ let open_temp_file ?buffer ?flags ?perm ?temp_dir ?prefix ?(suffix = "") () = Lwt.return (fname, chan)) (function | Unix.Unix_error _ when n < 1000 -> attempt (n + 1) - | exn -> Lwt.fail exn) + | exn -> Lwt.reraise exn) in attempt 0 @@ -1464,7 +1468,7 @@ let create_temp_dir Lwt.return name) (function | Unix.Unix_error (Unix.EEXIST, _, _) when n < 1000 -> attempt (n + 1) - | exn -> Lwt.fail exn) + | exn -> Lwt.reraise exn) in attempt 0 @@ -1485,10 +1489,10 @@ let win32_unlink fn = (* If everything succeeded but the final removal still failed, restore original permissions *) Lwt_unix.chmod fn st_perm >>= fun () -> - Lwt.fail exn) + Lwt.reraise exn) ) - (fun _ -> Lwt.fail exn) - | exn -> Lwt.fail exn) + (fun _ -> Lwt.reraise exn) + | exn -> Lwt.reraise exn) let unlink = if Sys.win32 then @@ -1545,7 +1549,7 @@ let close_socket fd = (function (* Occurs if the peer closes the connection first. *) | Unix.Unix_error (Unix.ENOTCONN, _, _) -> Lwt.return_unit - | exn -> Lwt.fail exn) [@ocaml.warning "-4"]) + | exn -> Lwt.reraise exn) [@ocaml.warning "-4"]) (fun () -> Lwt_unix.close fd) @@ -1570,7 +1574,7 @@ let open_connection ?fd ?in_buffer ?out_buffer sockaddr = ~mode:output (Lwt_bytes.write fd))) (fun exn -> Lwt_unix.close fd >>= fun () -> - Lwt.fail exn) + Lwt.reraise exn) let with_close_connection f (ic, oc) = (* If the user already tried to close the socket and got an exception, we @@ -1635,7 +1639,7 @@ let establish_server_generic (function | Unix.Unix_error (Unix.ECONNABORTED, _, _) -> Lwt.return `Try_again - | e -> Lwt.fail e) + | e -> Lwt.reraise e) in Lwt.pick [try_to_accept; should_stop] >>= function diff --git a/src/unix/lwt_libev_stubs.c b/src/unix/lwt_libev_stubs.c index ea390fa38e..e817fee347 100644 --- a/src/unix/lwt_libev_stubs.c +++ b/src/unix/lwt_libev_stubs.c @@ -6,7 +6,6 @@ /* Stubs for libev */ #include "lwt_config.h" -#include "lwt_unix.h" #if defined(HAVE_LIBEV) @@ -21,6 +20,8 @@ #include #include +#include "lwt_unix.h" + /* +-----------------------------------------------------------------+ | Backend types | +-----------------------------------------------------------------+ */ @@ -240,6 +241,8 @@ CAMLprim value lwt_libev_timer_stop(value loop, value val_watcher) { #else +#include "lwt_unix.h" + LWT_NOT_AVAILABLE1(libev_backend) LWT_NOT_AVAILABLE1(libev_init) LWT_NOT_AVAILABLE1(libev_stop) diff --git a/src/unix/lwt_main.ml b/src/unix/lwt_main.ml index 0cac449b87..356dc318f9 100644 --- a/src/unix/lwt_main.ml +++ b/src/unix/lwt_main.ml @@ -16,18 +16,14 @@ open Lwt.Infix let enter_iter_hooks = Lwt_sequence.create () let leave_iter_hooks = Lwt_sequence.create () -let yielded = Lwt_sequence.create () -let yield () = (Lwt.add_task_r [@ocaml.warning "-3"]) yielded +let yield = Lwt.pause let abandon_yielded_and_paused () = - Lwt_sequence.clear yielded; Lwt.abandon_paused () let run p = let rec run_loop () = - (* Fulfill paused promises now. *) - Lwt.wakeup_paused (); match Lwt.poll p with | Some x -> x @@ -36,20 +32,12 @@ let run p = Lwt_sequence.iter_l (fun f -> f ()) enter_iter_hooks; (* Do the main loop call. *) - let should_block_waiting_for_io = - Lwt.paused_count () = 0 && Lwt_sequence.is_empty yielded in + let should_block_waiting_for_io = Lwt.paused_count () = 0 in Lwt_engine.iter should_block_waiting_for_io; - (* Fulfill paused promises again. *) + (* Fulfill paused promises. *) Lwt.wakeup_paused (); - (* Fulfill yield promises. *) - if not (Lwt_sequence.is_empty yielded) then begin - let tmp = Lwt_sequence.create () in - Lwt_sequence.transfer_r yielded tmp; - Lwt_sequence.iter_l (fun resolver -> Lwt.wakeup resolver ()) tmp - end; - (* Call leave hooks. *) Lwt_sequence.iter_l (fun f -> f ()) leave_iter_hooks; @@ -119,7 +107,7 @@ let run p = | result -> finished (); result - | exception exn -> + | exception exn when Lwt.Exception_filter.run exn -> finished (); raise exn diff --git a/src/unix/lwt_main.mli b/src/unix/lwt_main.mli index 0d5c6b3dc7..f2ebde2196 100644 --- a/src/unix/lwt_main.mli +++ b/src/unix/lwt_main.mli @@ -39,6 +39,17 @@ let () = Lwt_main.run (main ()) [Lwt_main.run] will raise [Failure]. This should be considered a logic error (i.e., code making such a call is inherently broken). + In addition, note that if you have set the exception filter to let runtime + exceptions bubble up (via + [Lwt.Exception_filter.(set handle_all_except_runtime)]) + then Lwt does not attempt to catch exceptions thrown by the OCaml runtime. + Specifically, in this case, Lwt lets [Out_of_memory] and [Stack_overflow] + exceptions traverse all of its functions and bubble up to the caller of + [Lwt_main.run]. Moreover because these exceptions are left to traverse the + call stack, they leave the internal data-structures in an inconsistent + state. For this reason, calling [Lwt_main.run] again after such an + exception will raise [Failure]. + It is not safe to call [Lwt_main.run] in a function registered with [Stdlib.at_exit], use {!Lwt_main.at_exit} instead. *) @@ -49,22 +60,19 @@ val yield : unit -> unit Lwt.t [@@deprecated "Use Lwt.pause instead"] @deprecated Since 5.5.0 [yield] is deprecated in favor of the more general {!Lwt.pause} in order to avoid discrepancies in resolution (see below) and - stay compatible with other execution environments such as js_of_ocaml. - - Currently, paused promises are resolved more frequently than yielded promises. - The difference is unintended but existing applications could depend on it. - Unifying the two pools of promises into one in the future would eliminate - possible discrepancies and simplify the code. *) + stay compatible with other execution environments such as js_of_ocaml. *) -val abandon_yielded_and_paused : unit -> unit +val abandon_yielded_and_paused : unit -> unit [@@deprecated "Use Lwt.abandon_paused instead"] (** Causes promises created with {!Lwt.pause} and {!Lwt_main.yield} to remain forever pending. - [yield] is now deprecated in favor of the more general {!Lwt.pause}. - Once [yield] is phased out, this function will be deprecated as well. + (Note that [yield] is deprecated in favor of the more general {!Lwt.pause}.) This is meant for use with {!Lwt_unix.fork}, as a way to “abandon” more - promise chains that are pending in your process. *) + promise chains that are pending in your process. + + @deprecated Since 5.7 [abandon_yielded_and_paused] is deprecated in favour + of [Lwt.abandon_paused]. *) diff --git a/src/unix/lwt_preemptive.ml b/src/unix/lwt_preemptive.ml index 7e34e5d44b..357bd29fd1 100644 --- a/src/unix/lwt_preemptive.ml +++ b/src/unix/lwt_preemptive.ml @@ -182,7 +182,7 @@ let detach f args = let task () = try result := Result.Ok (f args) - with exn -> + with exn when Lwt.Exception_filter.run exn -> result := Result.Error exn in get_worker () >>= fun worker -> diff --git a/src/unix/lwt_process.cppo.ml b/src/unix/lwt_process.cppo.ml index 791e38ba71..ceb86be232 100644 --- a/src/unix/lwt_process.cppo.ml +++ b/src/unix/lwt_process.cppo.ml @@ -378,7 +378,7 @@ let read_opt read ic = (function | Unix.Unix_error (Unix.EPIPE, _, _) | End_of_file -> Lwt.return_none - | exn -> Lwt.fail exn) [@ocaml.warning "-4"] + | exn -> Lwt.reraise exn) [@ocaml.warning "-4"] let recv_chars pr = let ic = pr#stdout in @@ -512,8 +512,8 @@ let pmap ?timeout ?env ?cwd ?stderr cmd text = | Lwt.Canceled as exn -> (* Cancel the getter if the sender was canceled. *) Lwt.cancel getter; - Lwt.fail exn - | exn -> Lwt.fail exn) + Lwt.reraise exn + | exn -> Lwt.reraise exn) let pmap_chars ?timeout ?env ?cwd ?stderr cmd chars = let pr = open_process ?timeout ?env ?cwd ?stderr cmd in @@ -534,8 +534,8 @@ let pmap_line ?timeout ?env ?cwd ?stderr cmd line = | Lwt.Canceled as exn -> (* Cancel the getter if the sender was canceled. *) Lwt.cancel getter; - Lwt.fail exn - | exn -> Lwt.fail exn) + Lwt.reraise exn + | exn -> Lwt.reraise exn) let pmap_lines ?timeout ?env ?cwd ?stderr cmd lines = let pr = open_process ?timeout ?env ?cwd ?stderr cmd in diff --git a/src/unix/lwt_process_stubs.c b/src/unix/lwt_process_stubs.c index c57066452c..629682bac0 100644 --- a/src/unix/lwt_process_stubs.c +++ b/src/unix/lwt_process_stubs.c @@ -7,7 +7,7 @@ #if defined(LWT_ON_WINDOWS) -#include +#include "lwt_unix.h" #if OCAML_VERSION < 41300 #define CAML_INTERNALS diff --git a/src/unix/lwt_timeout.ml b/src/unix/lwt_timeout.ml index 77993038b3..088f32ebdc 100644 --- a/src/unix/lwt_timeout.ml +++ b/src/unix/lwt_timeout.ml @@ -72,7 +72,8 @@ let rec loop () = (*XXX Should probably report any exception *) try x.action () - with e -> !handle_exn e + with e when Lwt.Exception_filter.run e -> + !handle_exn e done; curr := (!curr + 1) mod (Array.length !buckets); if !count > 0 then loop () else begin stopped := true; Lwt.return_unit end) diff --git a/src/unix/lwt_unix.cppo.ml b/src/unix/lwt_unix.cppo.ml index 00443cffc3..5edb4b83d0 100644 --- a/src/unix/lwt_unix.cppo.ml +++ b/src/unix/lwt_unix.cppo.ml @@ -123,7 +123,7 @@ let sleep delay = Lwt.on_cancel waiter (fun () -> Lwt_engine.stop_event ev); waiter -let yield = (Lwt_main.yield [@warning "-3"]) +let yield = Lwt.pause let auto_yield timeout = let limit = ref (Unix.gettimeofday () +. timeout) in @@ -147,7 +147,7 @@ let auto_pause timeout = exception Timeout -let timeout d = sleep d >>= fun () -> Lwt.fail Timeout +let timeout d = sleep d >>= fun () -> raise Timeout let with_timeout d f = Lwt.pick [timeout d; Lwt.apply f ()] @@ -184,7 +184,7 @@ let wait_for_jobs () = let wrap_result f x = try Result.Ok (f x) - with exn -> + with exn when Lwt.Exception_filter.run exn -> Result.Error exn let run_job_aux async_method job result = @@ -244,7 +244,7 @@ external run_job_sync : 'a job -> 'a = "lwt_unix_run_job_sync" let self_result job = try Result.Ok (self_result job) - with exn -> + with exn when Lwt.Exception_filter.run exn -> Result.Error exn let in_retention_test = ref false @@ -267,7 +267,7 @@ let run_job ?async_method job = if async_method = Async_none then try Lwt.return (run_job_sync job) - with exn -> + with exn when Lwt.Exception_filter.run exn -> Lwt.fail exn else run_job_aux async_method job self_result @@ -519,7 +519,7 @@ let rec retry_syscall node event ch wakener action = Requeued Read | Retry_write -> Requeued Write - | e -> + | e when Lwt.Exception_filter.run e -> Exn e in match res with @@ -581,8 +581,8 @@ let wrap_syscall event ch action = register_action Read ch action | Retry_write -> register_action Write ch action - | e -> - Lwt.fail e + | e when Lwt.Exception_filter.run e -> + Lwt.reraise e (* +-----------------------------------------------------------------+ | Basic file input/output | @@ -636,7 +636,7 @@ let wait_read ch = Lwt.return_unit else register_action Read ch ignore) - Lwt.fail + Lwt.reraise external stub_read : Unix.file_descr -> Bytes.t -> int -> int -> int = "lwt_unix_read" external read_job : Unix.file_descr -> Bytes.t -> int -> int -> int job = "lwt_unix_read_job" @@ -694,7 +694,7 @@ let wait_write ch = Lwt.return_unit else register_action Write ch ignore) - Lwt.fail + Lwt.reraise external stub_write : Unix.file_descr -> Bytes.t -> int -> int -> int = "lwt_unix_write" external write_job : Unix.file_descr -> Bytes.t -> int -> int -> int job = "lwt_unix_write_job" @@ -1034,7 +1034,7 @@ let file_exists name = (fun e -> match e with | Unix.Unix_error _ -> Lwt.return_false - | _ -> Lwt.fail e) [@ocaml.warning "-4"] + | _ -> Lwt.reraise e) [@ocaml.warning "-4"] external utimes_job : string -> float -> float -> unit job = "lwt_unix_utimes_job" @@ -1140,7 +1140,7 @@ struct (fun e -> match e with | Unix.Unix_error _ -> Lwt.return_false - | _ -> Lwt.fail e) [@ocaml.warning "-4"] + | _ -> Lwt.reraise e) [@ocaml.warning "-4"] end @@ -1408,7 +1408,7 @@ let files_of_directory path = (fun () -> readdir_n handle chunk_size) (fun exn -> closedir handle >>= fun () -> - Lwt.fail exn) >>= fun entries -> + Lwt.reraise exn) >>= fun entries -> if Array.length entries < chunk_size then begin state := LDS_done; closedir handle >>= fun () -> @@ -1423,7 +1423,7 @@ let files_of_directory path = (fun () -> readdir_n handle chunk_size) (fun exn -> closedir handle >>= fun () -> - Lwt.fail exn) >>= fun entries -> + Lwt.reraise exn) >>= fun entries -> if Array.length entries < chunk_size then begin state := LDS_done; closedir handle >>= fun () -> @@ -2227,12 +2227,19 @@ let event_notifications = ref (Lwt_engine.on_readable (init_notification ()) han | Signals | +-----------------------------------------------------------------+ *) -external set_signal : int -> int -> unit = "lwt_unix_set_signal" -external remove_signal : int -> unit = "lwt_unix_remove_signal" +external set_signal : int -> int -> bool -> unit = "lwt_unix_set_signal" +external remove_signal : int -> bool -> unit = "lwt_unix_remove_signal" external init_signals : unit -> unit = "lwt_unix_init_signals" +external handle_signal : int -> unit = "lwt_unix_handle_signal" let () = init_signals () +let set_signal signum notification = + set_signal signum notification (Lwt_engine.forwards_signal signum) + +let remove_signal signum = + remove_signal signum (Lwt_engine.forwards_signal signum) + module Signal_map = Map.Make(struct type t = int let compare a b = a - b end) type signal_handler = { @@ -2265,7 +2272,7 @@ let on_signal_full signum handler = in (try set_signal signum notification - with exn -> + with exn when Lwt.Exception_filter.run exn -> stop_notification notification; raise exn); signals := Signal_map.add signum (notification, actions) !signals; @@ -2369,7 +2376,7 @@ let install_sigchld_handler () = Lwt_sequence.remove node; Lwt.wakeup wakener v end - with e -> + with e when Lwt.Exception_filter.run e -> Lwt_sequence.remove node; Lwt.wakeup_exn wakener e end wait_children) @@ -2388,7 +2395,7 @@ let () = let _waitpid flags pid = Lwt.catch (fun () -> Lwt.return (Unix.waitpid flags pid)) - Lwt.fail + Lwt.reraise let waitpid = if Sys.win32 then diff --git a/src/unix/lwt_unix.cppo.mli b/src/unix/lwt_unix.cppo.mli index 1685b405c8..df5d082067 100644 --- a/src/unix/lwt_unix.cppo.mli +++ b/src/unix/lwt_unix.cppo.mli @@ -854,6 +854,14 @@ val reinstall_signal_handler : int -> unit signal handler (with [Sys.set_signal]). This is useful in case another part of the program install another signal handler. *) +val handle_signal : int -> unit + (** [handle_signal signum] acts as if Lwt had received the [signum] signal. + This allows another IO library to install the handler, perform its own + handling, but still notify Lwt. It is particularly useful for SIGCHLD, + where several IO libraries may be spawning sub-processes. + + This function is thread-safe. *) + (** {2 Sockets} *) type inet_addr = Unix.inet_addr diff --git a/src/unix/lwt_unix.h b/src/unix/lwt_unix.h index 31d0e0472c..47f093016a 100644 --- a/src/unix/lwt_unix.h +++ b/src/unix/lwt_unix.h @@ -14,6 +14,11 @@ #include #include +#if OCAML_VERSION < 50000 +#define caml_convert_flag_list(flags, table) \ + caml_convert_flag_list((flags), (int *)(table)) +#endif + /* The macro to get the file-descriptor from a value. */ #if defined(LWT_ON_WINDOWS) #define FD_val(value) win_CRT_fd_of_filedescr(value) diff --git a/src/unix/lwt_unix_stubs.c b/src/unix/lwt_unix_stubs.c index bfbae6443c..443773baca 100644 --- a/src/unix/lwt_unix_stubs.c +++ b/src/unix/lwt_unix_stubs.c @@ -169,6 +169,7 @@ value lwt_unix_system_byte_order() { int lwt_unix_launch_thread(void *(*start)(void *), void *data) { pthread_t thread; pthread_attr_t attr; + sigset_t mask, old_mask; pthread_attr_init(&attr); @@ -176,9 +177,17 @@ int lwt_unix_launch_thread(void *(*start)(void *), void *data) { it when it terminates: */ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + /* Block all signals, otherwise ocaml handlers defined with the + module Sys may be executed in the new thread, oops... */ + sigfillset(&mask); + pthread_sigmask(SIG_SETMASK, &mask, &old_mask); + int zero_if_created_otherwise_errno = pthread_create(&thread, &attr, start, data); + /* Restore the signal mask for the calling thread. */ + pthread_sigmask(SIG_SETMASK, &old_mask, NULL); + pthread_attr_destroy(&attr); return zero_if_created_otherwise_errno; @@ -456,10 +465,11 @@ static void lwt_unix_socketpair(int domain, int type, int protocol, uerror("socketpair", Nothing); } -static int socket_domain_table[] = {PF_UNIX, PF_INET, PF_INET6}; +static const int socket_domain_table[] = + {PF_UNIX, PF_INET, PF_INET6}; -static int socket_type_table[] = {SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, - SOCK_SEQPACKET}; +static const int socket_type_table[] = + {SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_SEQPACKET}; CAMLprim value lwt_unix_socketpair_stub(value cloexec, value domain, value type, value protocol) { @@ -801,6 +811,11 @@ static void handle_signal(int signum) { } } +CAMLprim value lwt_unix_handle_signal(value val_signum) { + handle_signal(caml_convert_signal_number(Int_val(val_signum))); + return Val_unit; +} + #if defined(LWT_ON_WINDOWS) /* Handle Ctrl+C on windows. */ static BOOL WINAPI handle_break(DWORD event) { @@ -813,7 +828,7 @@ static BOOL WINAPI handle_break(DWORD event) { #endif /* Install a signal handler. */ -CAMLprim value lwt_unix_set_signal(value val_signum, value val_notification) { +CAMLprim value lwt_unix_set_signal(value val_signum, value val_notification, value val_forwarded) { #if !defined(LWT_ON_WINDOWS) struct sigaction sa; #endif @@ -825,6 +840,8 @@ CAMLprim value lwt_unix_set_signal(value val_signum, value val_notification) { signal_notifications[signum] = notification; + if (Bool_val(val_forwarded)) return Val_unit; + #if defined(LWT_ON_WINDOWS) if (signum == SIGINT) { if (!SetConsoleCtrlHandler(handle_break, TRUE)) { @@ -840,7 +857,11 @@ CAMLprim value lwt_unix_set_signal(value val_signum, value val_notification) { } #else sa.sa_handler = handle_signal; +#if OCAML_VERSION >= 50000 + sa.sa_flags = SA_ONSTACK; +#else sa.sa_flags = 0; +#endif sigemptyset(&sa.sa_mask); if (sigaction(signum, &sa, NULL) == -1) { signal_notifications[signum] = -1; @@ -851,7 +872,7 @@ CAMLprim value lwt_unix_set_signal(value val_signum, value val_notification) { } /* Remove a signal handler. */ -CAMLprim value lwt_unix_remove_signal(value val_signum) { +CAMLprim value lwt_unix_remove_signal(value val_signum, value val_forwarded) { #if !defined(LWT_ON_WINDOWS) struct sigaction sa; #endif @@ -859,6 +880,9 @@ CAMLprim value lwt_unix_remove_signal(value val_signum) { set_signal. */ int signum = caml_convert_signal_number(Int_val(val_signum)); signal_notifications[signum] = -1; + + if (Bool_val(val_forwarded)) return Val_unit; + #if defined(LWT_ON_WINDOWS) if (signum == SIGINT) SetConsoleCtrlHandler(NULL, FALSE); @@ -964,18 +988,11 @@ void initialize_threading() { | Worker loop | +-----------------------------------------------------------------+ */ -/* Function executed by threads of the pool. */ +/* Function executed by threads of the pool. + * Note: all signals are masked for this thread. */ static void *worker_loop(void *data) { lwt_unix_job job = (lwt_unix_job)data; -#if defined(HAVE_PTHREAD) - /* Block all signals, otherwise ocaml handlers defined with the - module Sys may be executed in this thread, oops... */ - sigset_t mask; - sigfillset(&mask); - pthread_sigmask(SIG_SETMASK, &mask, NULL); -#endif - /* Execute the initial job if any. */ if (job != NULL) execute_job(job); diff --git a/src/unix/unix_c/unix_access_job.c b/src/unix/unix_c/unix_access_job.c index a054fc34cb..2e7409356a 100644 --- a/src/unix/unix_c/unix_access_job.c +++ b/src/unix/unix_c/unix_access_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) @@ -34,7 +35,7 @@ +-----------------------------------------------------------------+ */ /* Table mapping constructors of ocaml type Unix.access_permission to C values. */ -static int access_permission_table[] = { +static const int access_permission_table[] = { /* Constructor R_OK. */ R_OK, /* Constructor W_OK. */ diff --git a/src/unix/unix_c/unix_bytes_recv.c b/src/unix/unix_c/unix_bytes_recv.c index 9de910bee4..fac0ebda8e 100644 --- a/src/unix/unix_c/unix_bytes_recv.c +++ b/src/unix/unix_c/unix_bytes_recv.c @@ -14,6 +14,7 @@ #include #include +#include "lwt_unix.h" #include "unix_recv_send_utils.h" value lwt_unix_bytes_recv(value fd, value buf, value ofs, value len, diff --git a/src/unix/unix_c/unix_bytes_recvfrom.c b/src/unix/unix_c/unix_bytes_recvfrom.c index db91759f7c..1ae1c65a1a 100644 --- a/src/unix/unix_c/unix_bytes_recvfrom.c +++ b/src/unix/unix_c/unix_bytes_recvfrom.c @@ -16,6 +16,7 @@ #include #include +#include "lwt_unix.h" #include "unix_recv_send_utils.h" value lwt_unix_bytes_recvfrom(value fd, value buf, value ofs, value len, diff --git a/src/unix/unix_c/unix_bytes_send.c b/src/unix/unix_c/unix_bytes_send.c index 9cd5a44280..2bdd44a348 100644 --- a/src/unix/unix_c/unix_bytes_send.c +++ b/src/unix/unix_c/unix_bytes_send.c @@ -14,6 +14,7 @@ #include #include +#include "lwt_unix.h" #include "unix_recv_send_utils.h" value lwt_unix_bytes_send(value fd, value buf, value ofs, value len, diff --git a/src/unix/unix_c/unix_bytes_sendto.c b/src/unix/unix_c/unix_bytes_sendto.c index ea34097d4f..e9610b8951 100644 --- a/src/unix/unix_c/unix_bytes_sendto.c +++ b/src/unix/unix_c/unix_bytes_sendto.c @@ -15,6 +15,7 @@ #include #include +#include "lwt_unix.h" #include "unix_recv_send_utils.h" value lwt_unix_bytes_sendto(value fd, value buf, value ofs, value len, diff --git a/src/unix/unix_c/unix_chdir_job.c b/src/unix/unix_c/unix_chdir_job.c index 87826c8bd4..7832e51a9c 100644 --- a/src/unix/unix_c/unix_chdir_job.c +++ b/src/unix/unix_c/unix_chdir_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_chmod_job.c b/src/unix/unix_c/unix_chmod_job.c index 75dadf88b2..b84117ea4f 100644 --- a/src/unix/unix_c/unix_chmod_job.c +++ b/src/unix/unix_c/unix_chmod_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_chown_job.c b/src/unix/unix_c/unix_chown_job.c index 4b484825ce..1188012621 100644 --- a/src/unix/unix_c/unix_chown_job.c +++ b/src/unix/unix_c/unix_chown_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_chroot_job.c b/src/unix/unix_c/unix_chroot_job.c index 3f836b1ac7..7fe625043e 100644 --- a/src/unix/unix_c/unix_chroot_job.c +++ b/src/unix/unix_c/unix_chroot_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_close_job.c b/src/unix/unix_c/unix_close_job.c index 483a85e3e8..31f26263ef 100644 --- a/src/unix/unix_c/unix_close_job.c +++ b/src/unix/unix_c/unix_close_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_fchmod_job.c b/src/unix/unix_c/unix_fchmod_job.c index a4095a1540..063a1f2945 100644 --- a/src/unix/unix_c/unix_fchmod_job.c +++ b/src/unix/unix_c/unix_fchmod_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_fchown_job.c b/src/unix/unix_c/unix_fchown_job.c index ecba81441d..9df088217c 100644 --- a/src/unix/unix_c/unix_fchown_job.c +++ b/src/unix/unix_c/unix_fchown_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_fdatasync_job.c b/src/unix/unix_c/unix_fdatasync_job.c index 40c1011a90..6d3c71a632 100644 --- a/src/unix/unix_c/unix_fdatasync_job.c +++ b/src/unix/unix_c/unix_fdatasync_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) && defined(HAVE_FDATASYNC) diff --git a/src/unix/unix_c/unix_fsync_job.c b/src/unix/unix_c/unix_fsync_job.c index 1f05e72dee..5d932403f3 100644 --- a/src/unix/unix_c/unix_fsync_job.c +++ b/src/unix/unix_c/unix_fsync_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_ftruncate_job.c b/src/unix/unix_c/unix_ftruncate_job.c index cf8cf00a41..fdac1721d7 100644 --- a/src/unix/unix_c/unix_ftruncate_job.c +++ b/src/unix/unix_c/unix_ftruncate_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_getaddrinfo_job.c b/src/unix/unix_c/unix_getaddrinfo_job.c index 395335d15f..d7b1f8d0de 100644 --- a/src/unix/unix_c/unix_getaddrinfo_job.c +++ b/src/unix/unix_c/unix_getaddrinfo_job.c @@ -29,7 +29,7 @@ struct job_getaddrinfo { char data[]; }; -static value cst_to_constr(int n, int *tbl, int size, int deflt) +static value cst_to_constr(int n, const int *tbl, int size, int deflt) { int i; for (i = 0; i < size; i++) @@ -37,7 +37,7 @@ static value cst_to_constr(int n, int *tbl, int size, int deflt) return Val_int(deflt); } -static value convert_addrinfo(struct addrinfo *a) +static value convert_addrinfo(const struct addrinfo *a) { CAMLparam0(); CAMLlocal3(vres, vaddr, vcanonname); diff --git a/src/unix/unix_c/unix_getnameinfo_job.c b/src/unix/unix_c/unix_getnameinfo_job.c index 3ae74091df..8f5e3bbdbd 100644 --- a/src/unix/unix_c/unix_getnameinfo_job.c +++ b/src/unix/unix_c/unix_getnameinfo_job.c @@ -28,8 +28,8 @@ struct job_getnameinfo { int result; }; -static int getnameinfo_flag_table[] = {NI_NOFQDN, NI_NUMERICHOST, NI_NAMEREQD, - NI_NUMERICSERV, NI_DGRAM}; +static const int getnameinfo_flag_table[] = + {NI_NOFQDN, NI_NUMERICHOST, NI_NAMEREQD, NI_NUMERICSERV, NI_DGRAM}; static void worker_getnameinfo(struct job_getnameinfo *job) { diff --git a/src/unix/unix_c/unix_getprotoby_getservby_job.c b/src/unix/unix_c/unix_getprotoby_getservby_job.c index 5169fc9b6e..88804d1ead 100644 --- a/src/unix/unix_c/unix_getprotoby_getservby_job.c +++ b/src/unix/unix_c/unix_getprotoby_getservby_job.c @@ -9,6 +9,7 @@ #define ARGS(args...) args +#include #include #include #include diff --git a/src/unix/unix_c/unix_link_job.c b/src/unix/unix_c/unix_link_job.c index 80ad2d38dd..3d51a240a3 100644 --- a/src/unix/unix_c/unix_link_job.c +++ b/src/unix/unix_c/unix_link_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_lockf_job.c b/src/unix/unix_c/unix_lockf_job.c index a0449d80fd..67732f8b01 100644 --- a/src/unix/unix_c/unix_lockf_job.c +++ b/src/unix/unix_c/unix_lockf_job.c @@ -84,8 +84,8 @@ static void worker_lockf(struct job_lockf *job) #else -static int lock_command_table[] = {F_ULOCK, F_LOCK, F_TLOCK, - F_TEST, F_LOCK, F_TLOCK}; +static const int lock_command_table[] = + {F_ULOCK, F_LOCK, F_TLOCK, F_TEST, F_LOCK, F_TLOCK}; static void worker_lockf(struct job_lockf *job) { diff --git a/src/unix/unix_c/unix_lseek_job.c b/src/unix/unix_c/unix_lseek_job.c index fe2740493d..7b8a4ac623 100644 --- a/src/unix/unix_c/unix_lseek_job.c +++ b/src/unix/unix_c/unix_lseek_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) @@ -35,7 +36,7 @@ +-----------------------------------------------------------------+ */ /* Table mapping constructors of ocaml type Unix.seek_command to C values. */ -static int seek_command_table[] = { +static const int seek_command_table[] = { /* Constructor SEEK_SET. */ SEEK_SET, /* Constructor SEEK_CUR. */ diff --git a/src/unix/unix_c/unix_madvise.c b/src/unix/unix_c/unix_madvise.c index f619a4441e..51b1d3ce99 100644 --- a/src/unix/unix_c/unix_madvise.c +++ b/src/unix/unix_c/unix_madvise.c @@ -12,7 +12,7 @@ #include #include -static int advise_table[] = { +static const int advise_table[] = { MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED, MADV_DONTNEED, #if defined(MADV_MERGEABLE) MADV_MERGEABLE, diff --git a/src/unix/unix_c/unix_mkdir_job.c b/src/unix/unix_c/unix_mkdir_job.c index 872432a839..3000ada806 100644 --- a/src/unix/unix_c/unix_mkdir_job.c +++ b/src/unix/unix_c/unix_mkdir_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_mkfifo_job.c b/src/unix/unix_c/unix_mkfifo_job.c index 98838c6148..ff1bf2f790 100644 --- a/src/unix/unix_c/unix_mkfifo_job.c +++ b/src/unix/unix_c/unix_mkfifo_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_open_job.c b/src/unix/unix_c/unix_open_job.c index a77ad1cabf..0c1be84591 100644 --- a/src/unix/unix_c/unix_open_job.c +++ b/src/unix/unix_c/unix_open_job.c @@ -35,7 +35,7 @@ #define caml_unix_cloexec_default unix_cloexec_default #endif -static int open_flag_table[] = { +static const int open_flag_table[] = { O_RDONLY, O_WRONLY, O_RDWR, O_NONBLOCK, O_APPEND, O_CREAT, O_TRUNC, O_EXCL, O_NOCTTY, O_DSYNC, O_SYNC, O_RSYNC, 0, /* O_SHARE_DELETE, Windows-only */ diff --git a/src/unix/unix_c/unix_recv.c b/src/unix/unix_c/unix_recv.c index 7ea757611a..4456d74210 100644 --- a/src/unix/unix_c/unix_recv.c +++ b/src/unix/unix_c/unix_recv.c @@ -13,6 +13,7 @@ #include #include +#include "lwt_unix.h" #include "unix_recv_send_utils.h" value lwt_unix_recv(value fd, value buf, value ofs, value len, value flags) diff --git a/src/unix/unix_c/unix_recv_send_utils.c b/src/unix/unix_c/unix_recv_send_utils.c index f9486afe9f..bbb3d9c1f5 100644 --- a/src/unix/unix_c/unix_recv_send_utils.c +++ b/src/unix/unix_c/unix_recv_send_utils.c @@ -19,7 +19,7 @@ #include "unix_recv_send_utils.h" -int msg_flag_table[3] = {MSG_OOB, MSG_DONTROUTE, MSG_PEEK}; +const int msg_flag_table[3] = {MSG_OOB, MSG_DONTROUTE, MSG_PEEK}; value wrapper_recv_msg(int fd, int n_iovs, struct iovec *iovs) { diff --git a/src/unix/unix_c/unix_recv_send_utils.h b/src/unix/unix_c/unix_recv_send_utils.h index f4b9bd76d4..407091f6e2 100644 --- a/src/unix/unix_c/unix_recv_send_utils.h +++ b/src/unix/unix_c/unix_recv_send_utils.h @@ -36,9 +36,15 @@ #define caml_unix_socket_type_table socket_type_table #endif -extern int msg_flag_table[]; +#if OCAML_VERSION < 50300 extern int caml_unix_socket_domain_table[]; extern int caml_unix_socket_type_table[]; +#else +extern const int caml_unix_socket_domain_table[]; +extern const int caml_unix_socket_type_table[]; +#endif + +extern const int msg_flag_table[]; extern void get_sockaddr(value mladdr, union sock_addr_union *addr /*out*/, socklen_t *addr_len /*out*/); value wrapper_recv_msg(int fd, int n_iovs, struct iovec *iovs); diff --git a/src/unix/unix_c/unix_recvfrom.c b/src/unix/unix_c/unix_recvfrom.c index d55a646cca..fd86c247a7 100644 --- a/src/unix/unix_c/unix_recvfrom.c +++ b/src/unix/unix_c/unix_recvfrom.c @@ -15,6 +15,7 @@ #include #include +#include "lwt_unix.h" #include "unix_recv_send_utils.h" value lwt_unix_recvfrom(value fd, value buf, value ofs, value len, value flags) diff --git a/src/unix/unix_c/unix_rename_job.c b/src/unix/unix_c/unix_rename_job.c index 35cd4f8abc..a8a3d4ff8b 100644 --- a/src/unix/unix_c/unix_rename_job.c +++ b/src/unix/unix_c/unix_rename_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_rmdir_job.c b/src/unix/unix_c/unix_rmdir_job.c index ad813a7eec..5a9d88a66d 100644 --- a/src/unix/unix_c/unix_rmdir_job.c +++ b/src/unix/unix_c/unix_rmdir_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_send.c b/src/unix/unix_c/unix_send.c index de053de7b3..fc4ba34bec 100644 --- a/src/unix/unix_c/unix_send.c +++ b/src/unix/unix_c/unix_send.c @@ -13,6 +13,7 @@ #include #include +#include "lwt_unix.h" #include "unix_recv_send_utils.h" value lwt_unix_send(value fd, value buf, value ofs, value len, value flags) diff --git a/src/unix/unix_c/unix_sendto.c b/src/unix/unix_c/unix_sendto.c index 6c0ac8488b..8a993e669d 100644 --- a/src/unix/unix_c/unix_sendto.c +++ b/src/unix/unix_c/unix_sendto.c @@ -14,6 +14,7 @@ #include #include +#include "lwt_unix.h" #include "unix_recv_send_utils.h" value lwt_unix_sendto(value fd, value buf, value ofs, value len, value flags, diff --git a/src/unix/unix_c/unix_symlink_job.c b/src/unix/unix_c/unix_symlink_job.c index e66e0cec0c..337ee24848 100644 --- a/src/unix/unix_c/unix_symlink_job.c +++ b/src/unix/unix_c/unix_symlink_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_tcdrain_job.c b/src/unix/unix_c/unix_tcdrain_job.c index fc2b41f99c..8b77877497 100644 --- a/src/unix/unix_c/unix_tcdrain_job.c +++ b/src/unix/unix_c/unix_tcdrain_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) && !defined(__ANDROID__) diff --git a/src/unix/unix_c/unix_tcflow_job.c b/src/unix/unix_c/unix_tcflow_job.c index 764fe3c577..69e76f9794 100644 --- a/src/unix/unix_c/unix_tcflow_job.c +++ b/src/unix/unix_c/unix_tcflow_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) @@ -35,7 +36,7 @@ +-----------------------------------------------------------------+ */ /* Table mapping constructors of ocaml type Unix.flow_action to C values. */ -static int flow_action_table[] = { +static const int flow_action_table[] = { /* Constructor TCOOFF. */ TCOOFF, /* Constructor TCOON. */ diff --git a/src/unix/unix_c/unix_tcflush_job.c b/src/unix/unix_c/unix_tcflush_job.c index 9287ca8e86..151485ac15 100644 --- a/src/unix/unix_c/unix_tcflush_job.c +++ b/src/unix/unix_c/unix_tcflush_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) @@ -35,7 +36,7 @@ +-----------------------------------------------------------------+ */ /* Table mapping constructors of ocaml type Unix.flush_queue to C values. */ -static int flush_queue_table[] = { +static const int flush_queue_table[] = { /* Constructor TCIFLUSH. */ TCIFLUSH, /* Constructor TCOFLUSH. */ diff --git a/src/unix/unix_c/unix_tcsendbreak_job.c b/src/unix/unix_c/unix_tcsendbreak_job.c index 23fa74919d..6b7c26c38b 100644 --- a/src/unix/unix_c/unix_tcsendbreak_job.c +++ b/src/unix/unix_c/unix_tcsendbreak_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_tcsetattr_job.c b/src/unix/unix_c/unix_tcsetattr_job.c index b699b50692..9d3029db28 100644 --- a/src/unix/unix_c/unix_tcsetattr_job.c +++ b/src/unix/unix_c/unix_tcsetattr_job.c @@ -24,7 +24,7 @@ struct job_tcsetattr { int error_code; }; -static int when_flag_table[] = {TCSANOW, TCSADRAIN, TCSAFLUSH}; +static const int when_flag_table[] = {TCSANOW, TCSADRAIN, TCSAFLUSH}; static void worker_tcsetattr(struct job_tcsetattr *job) { diff --git a/src/unix/unix_c/unix_termios_conversion.c b/src/unix/unix_c/unix_termios_conversion.c index e9b82f8e6f..2cd5f2eef7 100644 --- a/src/unix/unix_c/unix_termios_conversion.c +++ b/src/unix/unix_c/unix_termios_conversion.c @@ -20,7 +20,7 @@ enum { Iflags, Oflags, Cflags, Lflags }; /* Structure of the terminal_io record. Cf. unix.mli */ -static long terminal_io_descr[] = { +static const long terminal_io_descr[] = { /* Input modes */ Bool, Iflags, IGNBRK, Bool, Iflags, BRKINT, Bool, Iflags, IGNPAR, Bool, Iflags, PARMRK, Bool, Iflags, INPCK, Bool, Iflags, ISTRIP, Bool, Iflags, @@ -40,7 +40,7 @@ static long terminal_io_descr[] = { Char, VINTR, Char, VQUIT, Char, VERASE, Char, VKILL, Char, VEOF, Char, VEOL, Char, VMIN, Char, VTIME, Char, VSTART, Char, VSTOP, End}; -static struct { +static const struct { speed_t speed; int baud; } speedtable[] = {{B50, 50}, @@ -132,7 +132,7 @@ static struct { #endif }; -#define NSPEEDS (sizeof(speedtable) / sizeof(speedtable[0])) +#define NSPEEDS (int)(sizeof(speedtable) / sizeof(speedtable[0])) static tcflag_t *choose_field(struct termios *terminal_status, long field) { @@ -152,10 +152,7 @@ static tcflag_t *choose_field(struct termios *terminal_status, long field) void encode_terminal_status(struct termios *terminal_status, volatile value *dst) { - long *pc; - int i; - - for (pc = terminal_io_descr; *pc != End; dst++) { + for (const long *pc = terminal_io_descr; *pc != End; dst++) { switch (*pc++) { case Bool: { tcflag_t *src = choose_field(terminal_status, *pc++); @@ -168,7 +165,7 @@ void encode_terminal_status(struct termios *terminal_status, volatile value *dst int ofs = *pc++; int num = *pc++; tcflag_t msk = *pc++; - for (i = 0; i < num; i++) { + for (int i = 0; i < num; i++) { if ((*src & msk) == pc[i]) { *dst = Val_int(i + ofs); break; @@ -190,7 +187,7 @@ void encode_terminal_status(struct termios *terminal_status, volatile value *dst speed = cfgetispeed(terminal_status); break; } - for (i = 0; i < NSPEEDS; i++) { + for (int i = 0; i < NSPEEDS; i++) { if (speed == speedtable[i].speed) { *dst = Val_int(speedtable[i].baud); break; @@ -209,10 +206,7 @@ void encode_terminal_status(struct termios *terminal_status, volatile value *dst int decode_terminal_status(struct termios *terminal_status, volatile value *src) { - long *pc; - int i; - - for (pc = terminal_io_descr; *pc != End; src++) { + for (const long *pc = terminal_io_descr; *pc != End; src++) { switch (*pc++) { case Bool: { tcflag_t *dst = choose_field(terminal_status, *pc++); @@ -228,7 +222,7 @@ int decode_terminal_status(struct termios *terminal_status, volatile value *src) int ofs = *pc++; int num = *pc++; tcflag_t msk = *pc++; - i = Int_val(*src) - ofs; + int i = Int_val(*src) - ofs; if (i >= 0 && i < num) { *dst = (*dst & ~msk) | pc[i]; } else { @@ -242,7 +236,7 @@ int decode_terminal_status(struct termios *terminal_status, volatile value *src) int which = *pc++; int baud = Int_val(*src); int res = 0; - for (i = 0; i < NSPEEDS; i++) { + for (int i = 0; i < NSPEEDS; i++) { if (baud == speedtable[i].baud) { switch (which) { case Output: diff --git a/src/unix/unix_c/unix_truncate_job.c b/src/unix/unix_c/unix_truncate_job.c index ea88f1de24..9f647aa6c4 100644 --- a/src/unix/unix_c/unix_truncate_job.c +++ b/src/unix/unix_c/unix_truncate_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_unlink_job.c b/src/unix/unix_c/unix_unlink_job.c index 8dbdb8c764..5385f53cd0 100644 --- a/src/unix/unix_c/unix_unlink_job.c +++ b/src/unix/unix_c/unix_unlink_job.c @@ -16,11 +16,12 @@ */ /* Caml headers. */ -#include +#include "lwt_config.h" #include #include #include #include +#include "lwt_unix.h" #if !defined(LWT_ON_WINDOWS) diff --git a/src/unix/unix_c/unix_wait4.c b/src/unix/unix_c/unix_wait4.c index daa9ab4f49..ee0acf5e8d 100644 --- a/src/unix/unix_c/unix_wait4.c +++ b/src/unix/unix_c/unix_wait4.c @@ -12,6 +12,7 @@ #include #include #include +#include "lwt_unix.h" #include #include @@ -58,7 +59,7 @@ static value alloc_process_status(int status) return st; } -static int wait_flag_table[] = {WNOHANG, WUNTRACED}; +static const int wait_flag_table[] = {WNOHANG, WUNTRACED}; value lwt_unix_wait4(value flags, value pid_req) { diff --git a/test/core/test_lwt.ml b/test/core/test_lwt.ml index 9097138642..2ba5ab5641 100644 --- a/test/core/test_lwt.ml +++ b/test/core/test_lwt.ml @@ -549,6 +549,15 @@ let catch_tests = suite "catch" [ state_is (Lwt.Return Exception) p end; + test "rejected (raise)" begin fun () -> + let p = + Lwt.catch + (fun () -> raise Exception) + (fun exn -> Lwt.return exn) + in + state_is (Lwt.Return Exception) p + end; + (* This is an analog of the "bind quirk," see https://github.com/ocsigen/lwt/issues/329 *) @@ -635,6 +644,42 @@ let catch_tests = suite "catch" [ Lwt.wakeup_exn r1 Exit; p4 end; + + + test "catch with ocaml-runtime exception" begin fun () -> + Lwt.Exception_filter.(set handle_all_except_runtime); + try + Lwt.catch + (fun () -> raise Out_of_memory) + (fun _ -> Lwt.return_false) + with + | Out_of_memory -> Lwt.return_true + end; + + test "try_bind with ocaml-runtime exception" begin fun () -> + Lwt.Exception_filter.(set handle_all_except_runtime); + try + Lwt.try_bind + (fun () -> raise Out_of_memory) + (fun () -> Lwt.return_false) + (fun _ -> Lwt.return_false) + with + | Out_of_memory -> Lwt.return_true + end; + + test "try_bind(2) with ocaml-runtime exception" begin fun () -> + Lwt.Exception_filter.(set handle_all_except_runtime); + try + let _ = + Lwt.try_bind + (fun () -> Lwt.return_unit) + (fun () -> raise Out_of_memory) + (fun _ -> Lwt.return_false) + in + Lwt.return_false + with + | Out_of_memory -> Lwt.return_true + end; ] let suites = suites @ [catch_tests] @@ -660,7 +705,7 @@ let backtrace_catch_tests = suite "backtrace_catch" [ test "rejected" begin fun () -> let p = Lwt.backtrace_catch add_loc - (fun () -> Lwt.fail Exception) + (fun () -> raise Exception) (fun exn -> Lwt.return exn) in state_is (Lwt.Return Exception) p @@ -753,7 +798,7 @@ let try_bind_tests = suite "try_bind" [ test "rejected" begin fun () -> let p = Lwt.try_bind - (fun () -> Lwt.fail Exception) + (fun () -> raise Exception) (fun _ -> Lwt.return Exit) (fun exn -> Lwt.return exn) in @@ -774,7 +819,7 @@ let try_bind_tests = suite "try_bind" [ test "rejected, h raises" begin fun () -> try ignore @@ Lwt.try_bind - (fun () -> Lwt.fail Exit) + (fun () -> raise Exit) (fun _ -> Lwt.return_unit) (fun _ -> raise Exception); Lwt.return_false @@ -925,7 +970,7 @@ let backtrace_try_bind_tests = suite "backtrace_try_bind" [ test "rejected" begin fun () -> let p = Lwt.backtrace_try_bind add_loc - (fun () -> Lwt.fail Exception) + (fun () -> raise Exception) (fun _ -> Lwt.return Exit) (fun exn -> Lwt.return exn) in @@ -1096,7 +1141,7 @@ let finalize_tests = suite "finalize" [ test "rejected, f' raises" begin fun () -> try ignore @@ Lwt.finalize - (fun () -> Lwt.fail Exit) + (fun () -> raise Exit) (fun () -> raise Exception); Lwt.return_false with Exception -> @@ -1133,7 +1178,7 @@ let finalize_tests = suite "finalize" [ let p = Lwt.finalize (fun () -> p) - (fun () -> Lwt.fail Exception) + (fun () -> raise Exception) in Lwt.wakeup r (); state_is (Lwt.Fail Exception) p @@ -1196,7 +1241,7 @@ let finalize_tests = suite "finalize" [ let p = Lwt.finalize (fun () -> p) - (fun () -> Lwt.fail Exception) + (fun () -> raise Exception) in Lwt.wakeup_exn r Exit; state_is (Lwt.Fail Exception) p @@ -1311,7 +1356,7 @@ let backtrace_finalize_tests = suite "backtrace_finalize" [ let f'_ran = ref false in let p = Lwt.backtrace_finalize add_loc - (fun () -> Lwt.fail Exception) + (fun () -> raise Exception) (fun () -> f'_ran := true; Lwt.return_unit) in Lwt.bind (state_is (Lwt.Fail Exception) p) (fun correct -> @@ -1331,7 +1376,7 @@ let backtrace_finalize_tests = suite "backtrace_finalize" [ test "rejected, f' raises" begin fun () -> try ignore @@ Lwt.backtrace_finalize add_loc - (fun () -> Lwt.fail Exit) + (fun () -> raise Exit) (fun () -> raise Exception); Lwt.return_false with Exception -> @@ -1368,7 +1413,7 @@ let backtrace_finalize_tests = suite "backtrace_finalize" [ let p = Lwt.backtrace_finalize add_loc (fun () -> p) - (fun () -> Lwt.fail Exception) + (fun () -> raise Exception) in Lwt.wakeup r (); state_is (Lwt.Fail Exception) p @@ -1403,7 +1448,7 @@ let backtrace_finalize_tests = suite "backtrace_finalize" [ let p = Lwt.backtrace_finalize add_loc (fun () -> p) - (fun () -> Lwt.fail Exception) + (fun () -> raise Exception) in Lwt.wakeup_exn r Exit; state_is (Lwt.Fail Exception) p @@ -1767,7 +1812,7 @@ let async_tests = suite "async" [ let saw = ref None in let restore = set_async_exception_hook (fun exn -> saw := Some exn) in - Lwt.async (fun () -> Lwt.fail Exception); + Lwt.async (fun () -> raise Exception); later (fun () -> restore (); !saw = Some Exception) @@ -1816,7 +1861,7 @@ let dont_wait_tests = suite "dont_wait" [ test "rejected" begin fun () -> let saw = ref None in Lwt.dont_wait - (fun () -> Lwt.fail Exception) + (fun () -> raise Exception) (fun exn -> saw := Some exn); later (fun () -> !saw = Some Exception) end; @@ -3335,7 +3380,7 @@ let cancel_catch_tests = suite "cancel catch" [ test "task, pending, canceled, on_cancel, forwarded" begin fun () -> let on_cancel_2_ran = ref false in let p, _ = Lwt.task () in - let p' = Lwt.catch (fun () -> p) Lwt.fail in + let p' = Lwt.catch (fun () -> p) Lwt.reraise in Lwt.on_cancel p' (fun () -> on_cancel_2_ran := true); Lwt.cancel p'; Lwt.return @@ -3859,7 +3904,7 @@ let storage_tests = suite "storage" [ Lwt.with_value key (Some 42) (fun () -> let p' = Lwt.with_value key (Some 1337) (fun () -> - Lwt.try_bind (fun () -> p) f Lwt.fail) + Lwt.try_bind (fun () -> p) f Lwt.reraise) in Lwt.wakeup r (); Lwt.return diff --git a/test/core/test_lwt_pool.ml b/test/core/test_lwt_pool.ml index cd7bf27065..942ca0d7e8 100644 --- a/test/core/test_lwt_pool.ml +++ b/test/core/test_lwt_pool.ml @@ -16,7 +16,7 @@ let suite = suite "lwt_pool" [ end; test "creator exception" begin fun () -> - let gen = fun () -> Lwt.fail Dummy_error in + let gen = fun () -> raise Dummy_error in let p = Lwt_pool.create 1 gen in let u = Lwt_pool.use p (fun _ -> Lwt.return 0) in Lwt.return (Lwt.state u = Lwt.Fail Dummy_error) @@ -42,7 +42,7 @@ let suite = suite "lwt_pool" [ test "validation exceptions are propagated to users" begin fun () -> let c = Lwt_condition.create () in let gen = (fun () -> let l = ref 0 in Lwt.return l) in - let v l = if !l = 0 then Lwt.return_true else Lwt.fail Dummy_error in + let v l = if !l = 0 then Lwt.return_true else raise Dummy_error in let p = Lwt_pool.create 1 ~validate:v gen in let u1 = Lwt_pool.use p (fun l -> l := 1; Lwt_condition.wait c) in let u2 = Lwt_pool.use p (fun l -> Lwt.return !l) in @@ -106,7 +106,7 @@ let suite = suite "lwt_pool" [ test "waiter are notified on replacement" begin fun () -> let c = Lwt_condition.create () in let gen = (fun () -> let l = ref 0 in Lwt.return l) in - let v l = if !l = 0 then Lwt.return_true else Lwt.fail Dummy_error in + let v l = if !l = 0 then Lwt.return_true else raise Dummy_error in let p = Lwt_pool.create 1 ~validate:v gen in let u1 = Lwt_pool.use p (fun l -> l := 1; Lwt_condition.wait c) in let u2 = Lwt_pool.use p (fun l -> Lwt.return !l) in @@ -128,9 +128,9 @@ let suite = suite "lwt_pool" [ if !k then let l = ref 0 in Lwt.return l else - Lwt.fail Dummy_error + raise Dummy_error in - let v l = if !l = 0 then Lwt.return_true else Lwt.fail Dummy_error in + let v l = if !l = 0 then Lwt.return_true else raise Dummy_error in let p = Lwt_pool.create 1 ~validate:v gen in let u1 = Lwt_pool.use p (fun l -> l := 1; k:= false; Lwt_condition.wait c) in let u2 = Lwt_pool.use p (fun l -> Lwt.return !l) in @@ -156,7 +156,7 @@ let suite = suite "lwt_pool" [ let cond = Lwt_condition.create() in let p = Lwt_pool.create 1 ~validate:v ~check:c gen in let _ = Lwt_pool.use p (fun l -> l := 1; Lwt_condition.wait cond) in - let _ = Lwt_pool.use p (fun l -> l := 2; Lwt.fail Dummy_error) in + let _ = Lwt_pool.use p (fun l -> l := 2; raise Dummy_error) in let u3 = Lwt_pool.use p (fun l -> Lwt.return !l) in let () = Lwt_condition.signal cond "done" in Lwt.bind u3 (fun v -> @@ -169,7 +169,7 @@ let suite = suite "lwt_pool" [ let p = Lwt_pool.create 1 gen in let _ = Lwt_pool.use p (fun l -> Lwt.bind (Lwt_condition.wait cond) - (fun _ -> l:= 1; Lwt.fail Dummy_error)) in + (fun _ -> l:= 1; raise Dummy_error)) in let u2 = Lwt_pool.use p (fun l -> Lwt.return !l) in let () = Lwt_condition.signal cond "done" in Lwt.bind u2 (fun v -> diff --git a/test/core/test_lwt_result.ml b/test/core/test_lwt_result.ml index 8e354b6308..2f91ef5e62 100644 --- a/test/core/test_lwt_result.ml +++ b/test/core/test_lwt_result.ml @@ -93,7 +93,7 @@ let suite = test "catch, error case" (fun () -> - let x () = Lwt.fail Dummy_error in + let x () = raise Dummy_error in Lwt.return (Lwt_result.catch x = Lwt_result.fail Dummy_error) ); diff --git a/test/core/test_lwt_stream.ml b/test/core/test_lwt_stream.ml index a19f0b3df5..3e5193dc85 100644 --- a/test/core/test_lwt_stream.ml +++ b/test/core/test_lwt_stream.ml @@ -11,7 +11,7 @@ let expect_exit f = Lwt.return_false) (function | Exit -> Lwt.return_true - | e -> Lwt.fail e) + | e -> Lwt.reraise e) let suite = suite "lwt_stream" [ test "from" @@ -351,7 +351,7 @@ let suite = suite "lwt_stream" [ return (Some x) | (Result.Error e)::l -> q := l; - Lwt.fail e) + raise e) in Lwt_stream.to_list (Lwt_stream.wrap_exn stream) >>= fun l' -> return (l = l')); @@ -418,7 +418,7 @@ let suite = suite "lwt_stream" [ test "exception passing: basic, from" (fun () -> - let stream = Lwt_stream.from (fun () -> Lwt.fail Exit) in + let stream = Lwt_stream.from (fun () -> raise Exit) in expect_exit (fun () -> Lwt_stream.get stream)); test "exception passing: basic, from_direct" @@ -428,12 +428,12 @@ let suite = suite "lwt_stream" [ test "exception passing: to_list" (fun () -> - let stream = Lwt_stream.from (fun () -> Lwt.fail Exit) in + let stream = Lwt_stream.from (fun () -> raise Exit) in expect_exit (fun () -> Lwt_stream.to_list stream)); test "exception passing: mapped" (fun () -> - let stream = Lwt_stream.from (fun () -> Lwt.fail Exit) in + let stream = Lwt_stream.from (fun () -> raise Exit) in let stream = Lwt_stream.map (fun v -> v) stream in expect_exit (fun () -> Lwt_stream.get stream)); diff --git a/test/unix/dune b/test/unix/dune index 4e35bc6509..a918f6bb7c 100644 --- a/test/unix/dune +++ b/test/unix/dune @@ -1,7 +1,17 @@ (library (name tester) (libraries lwt lwttester) - (modules (:standard \ main luv_main dummy) )) + (modules + (:standard + \ + main + dummy + ocaml_runtime_exc_1 + ocaml_runtime_exc_2 + ocaml_runtime_exc_3 + ocaml_runtime_exc_4 + ocaml_runtime_exc_5 + ocaml_runtime_exc_6))) (executable (name dummy) @@ -14,9 +24,34 @@ (modules main)) (executable - (name luv_main) - (libraries lwt lwt_luv lwttester tester) - (modules luv_main)) + (name ocaml_runtime_exc_1) + (libraries lwt lwt.unix) + (modules ocaml_runtime_exc_1)) + +(executable + (name ocaml_runtime_exc_2) + (libraries lwt lwt.unix) + (modules ocaml_runtime_exc_2)) + +(executable + (name ocaml_runtime_exc_3) + (libraries lwt lwt.unix) + (modules ocaml_runtime_exc_3)) + +(executable + (name ocaml_runtime_exc_4) + (libraries lwt lwt.unix) + (modules ocaml_runtime_exc_4)) + +(executable + (name ocaml_runtime_exc_5) + (libraries lwt lwt.unix) + (modules ocaml_runtime_exc_5)) + +(executable + (name ocaml_runtime_exc_6) + (libraries lwt lwt.unix) + (modules ocaml_runtime_exc_6)) (alias (name runtest) @@ -24,10 +59,3 @@ (action (run %{exe:main.exe})) (deps bytes_io_data %{exe:dummy.exe}) ) - -(alias - (name runtest_libuv) - (package lwt) - (action (run %{exe:luv_main.exe})) - (deps bytes_io_data) -) diff --git a/test/unix/luv_main.ml b/test/unix/luv_main.ml deleted file mode 100644 index 38578b1839..0000000000 --- a/test/unix/luv_main.ml +++ /dev/null @@ -1,20 +0,0 @@ -(* This file is part of Lwt, released under the MIT license. See LICENSE.md for - details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *) - -open Tester - -let () = Lwt_engine.set (new Lwt_luv.engine) - -let () = - Test.concurrent "unix with luv" [ - Test_lwt_unix.suite; - Test_lwt_io.suite; - Test_lwt_io_non_block.suite; - Test_lwt_process.suite; - Test_lwt_engine.suite; - Test_mcast.suite; - Test_lwt_fmt.suite; - Test_lwt_timeout.suite; - Test_lwt_bytes.suite; - Test_sleep_and_timeout.suite; - ] diff --git a/test/unix/ocaml_runtime_exc_1.ml b/test/unix/ocaml_runtime_exc_1.ml new file mode 100644 index 0000000000..0b5144d409 --- /dev/null +++ b/test/unix/ocaml_runtime_exc_1.ml @@ -0,0 +1,29 @@ +(* This file is part of Lwt, released under the MIT license. See LICENSE.md for + details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *) + +(* set the exception filter being tested *) +let () = Lwt.Exception_filter.(set handle_all_except_runtime) + +(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a + different way than other exceptions and they leave the Lwt main loop in an + inconsistent state where it cannot be restarted. Indeed, attempting to call + [Lwt_main.run] again after it has crashed with a runtime exception causes a + "Nested calls to Lwt_main.run are not allowed" error. + + For this reason, we run this test as its own executable rather than as part + of a larger suite. *) + +open Lwt.Syntax + +let test () = + try + let () = Lwt_main.run ( + let* () = Lwt.pause () in + if true then raise Out_of_memory else Lwt.return_unit + ) in + Printf.eprintf "Test run+raise failure\n"; + Stdlib.exit 1 + with + | Out_of_memory -> () + +let () = test () diff --git a/test/unix/ocaml_runtime_exc_2.ml b/test/unix/ocaml_runtime_exc_2.ml new file mode 100644 index 0000000000..9d8b1c2719 --- /dev/null +++ b/test/unix/ocaml_runtime_exc_2.ml @@ -0,0 +1,30 @@ +(* This file is part of Lwt, released under the MIT license. See LICENSE.md for + details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *) + +(* set the exception filter being tested *) +let () = Lwt.Exception_filter.(set handle_all_except_runtime) + +(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a + different way than other exceptions and they leave the Lwt main loop in an + inconsistent state where it cannot be restarted. Indeed, attempting to call + [Lwt_main.run] again after it has crashed with a runtime exception causes a + "Nested calls to Lwt_main.run are not allowed" error. + + For this reason, we run this test as its own executable rather than as part + of a larger suite. *) + +open Lwt.Syntax + +let test () = + try + let () = Lwt_main.run ( + let* () = Lwt_unix.sleep 0.001 in + if true then raise Out_of_memory else Lwt.return_unit + ) in + Printf.eprintf "Test run+raise failure\n"; + Stdlib.exit 1 + with + | Out_of_memory -> () + +let () = test () + diff --git a/test/unix/ocaml_runtime_exc_3.ml b/test/unix/ocaml_runtime_exc_3.ml new file mode 100644 index 0000000000..b92fa4664a --- /dev/null +++ b/test/unix/ocaml_runtime_exc_3.ml @@ -0,0 +1,34 @@ +(* This file is part of Lwt, released under the MIT license. See LICENSE.md for + details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *) + +(* set the exception filter being tested *) +let () = Lwt.Exception_filter.(set handle_all_except_runtime) + +(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a + different way than other exceptions and they leave the Lwt main loop in an + inconsistent state where it cannot be restarted. Indeed, attempting to call + [Lwt_main.run] again after it has crashed with a runtime exception causes a + "Nested calls to Lwt_main.run are not allowed" error. + + For this reason, we run this test as its own executable rather than as part + of a larger suite. *) + +open Lwt.Syntax + +let test () = + try + let () = Lwt_main.run ( + let* () = Lwt.pause () in + Lwt.choose [ + (let* () = Lwt.pause () in raise Out_of_memory); + Lwt_unix.sleep 2.; + ] + ) in + Printf.eprintf "Test run+raise failure\n"; + Stdlib.exit 1 + with + | Out_of_memory -> () + +let () = test () + + diff --git a/test/unix/ocaml_runtime_exc_4.ml b/test/unix/ocaml_runtime_exc_4.ml new file mode 100644 index 0000000000..7f2071a471 --- /dev/null +++ b/test/unix/ocaml_runtime_exc_4.ml @@ -0,0 +1,33 @@ +(* This file is part of Lwt, released under the MIT license. See LICENSE.md for + details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *) + +(* set the exception filter being tested *) +let () = Lwt.Exception_filter.(set handle_all_except_runtime) + +(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a + different way than other exceptions and they leave the Lwt main loop in an + inconsistent state where it cannot be restarted. Indeed, attempting to call + [Lwt_main.run] again after it has crashed with a runtime exception causes a + "Nested calls to Lwt_main.run are not allowed" error. + + For this reason, we run this test as its own executable rather than as part + of a larger suite. *) + +open Lwt.Syntax + +let test () = + try + let () = Lwt_main.run ( + let* () = Lwt.pause () in + Lwt.catch + (fun () -> raise Out_of_memory) + (fun _ -> Lwt.return_unit) + ) in + Printf.eprintf "Test run+raise failure\n"; + Stdlib.exit 1 + with + | Out_of_memory -> () + +let () = test () + + diff --git a/test/unix/ocaml_runtime_exc_5.ml b/test/unix/ocaml_runtime_exc_5.ml new file mode 100644 index 0000000000..460e9c5d20 --- /dev/null +++ b/test/unix/ocaml_runtime_exc_5.ml @@ -0,0 +1,35 @@ +(* This file is part of Lwt, released under the MIT license. See LICENSE.md for + details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *) + +(* set the exception filter being tested *) +let () = Lwt.Exception_filter.(set handle_all_except_runtime) + +(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a + different way than other exceptions and they leave the Lwt main loop in an + inconsistent state where it cannot be restarted. Indeed, attempting to call + [Lwt_main.run] again after it has crashed with a runtime exception causes a + "Nested calls to Lwt_main.run are not allowed" error. + + For this reason, we run this test as its own executable rather than as part + of a larger suite. *) + +open Lwt.Syntax + +let test () = + try + let () = Lwt_main.run ( + let* () = Lwt.pause () in + let _ = + Lwt.async + (fun () -> let* () = Lwt.pause () in raise Out_of_memory) + in + Lwt_unix.sleep 0.5 + ) in + Printf.eprintf "Test run+raise failure\n"; + Stdlib.exit 1 + with + | Out_of_memory -> () + +let () = test () + + diff --git a/test/unix/ocaml_runtime_exc_6.ml b/test/unix/ocaml_runtime_exc_6.ml new file mode 100644 index 0000000000..efdbe8209c --- /dev/null +++ b/test/unix/ocaml_runtime_exc_6.ml @@ -0,0 +1,36 @@ +(* This file is part of Lwt, released under the MIT license. See LICENSE.md for + details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *) + +(* set the exception filter being tested *) +let () = Lwt.Exception_filter.(set handle_all_except_runtime) + +(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a + different way than other exceptions and they leave the Lwt main loop in an + inconsistent state where it cannot be restarted. Indeed, attempting to call + [Lwt_main.run] again after it has crashed with a runtime exception causes a + "Nested calls to Lwt_main.run are not allowed" error. + + For this reason, we run this test as its own executable rather than as part + of a larger suite. *) + +open Lwt.Syntax + +let test () = + try + let () = Lwt_main.run ( + let* () = Lwt.pause () in + let _ = + Lwt.dont_wait + (fun () -> let* () = Lwt.pause () in raise Out_of_memory) + (fun _ -> ()) + in + Lwt_unix.sleep 0.5 + ) in + Printf.eprintf "Test run+raise failure\n"; + Stdlib.exit 1 + with + | Out_of_memory -> () + +let () = test () + + diff --git a/test/unix/test_lwt_bytes.ml b/test/unix/test_lwt_bytes.ml index e29843ca8c..6de438b8de 100644 --- a/test/unix/test_lwt_bytes.ml +++ b/test/unix/test_lwt_bytes.ml @@ -793,7 +793,7 @@ let suite = suite "lwt_bytes" [ ) (function | Invalid_argument _message -> Lwt.return_true - | exn -> Lwt.fail exn + | exn -> Lwt.reraise exn ) end; @@ -825,7 +825,7 @@ let suite = suite "lwt_bytes" [ ) (function | Invalid_argument _message -> Lwt.return_true - | exn -> Lwt.fail exn + | exn -> Lwt.reraise exn ) end; @@ -839,7 +839,7 @@ let suite = suite "lwt_bytes" [ ) (function | Invalid_argument _message -> Lwt.return_true - | exn -> Lwt.fail exn + | exn -> Lwt.reraise exn ) end; ] diff --git a/test/unix/test_lwt_engine.ml b/test/unix/test_lwt_engine.ml index 38c7dcff11..3b6a5d231e 100644 --- a/test/unix/test_lwt_engine.ml +++ b/test/unix/test_lwt_engine.ml @@ -49,7 +49,7 @@ let timing_tests = [ | Lwt_unix.Timeout -> Lwt.return (Unix.gettimeofday ()) | exn -> - Lwt.fail exn) + Lwt.reraise exn) >>= fun stop -> diff --git a/test/unix/test_lwt_io.ml b/test/unix/test_lwt_io.ml index f6bad19cd7..f902f5e3ed 100644 --- a/test/unix/test_lwt_io.ml +++ b/test/unix/test_lwt_io.ml @@ -320,7 +320,7 @@ let suite = suite "lwt_io" [ exceptions_observed := !exceptions_observed + 1; Lwt.return_unit | exn -> - Lwt.fail exn) [@ocaml.warning "-4"] + Lwt.reraise exn) [@ocaml.warning "-4"] in let fd_r, fd_w = Lwt_unix.pipe () in @@ -353,7 +353,7 @@ let suite = suite "lwt_io" [ let filename = ref "." in let wrap f (filename', chan) = filename := filename'; f chan in let write_data chan = Lwt_io.write chan "test file content" in - let write_data_fail _ = Lwt.fail Dummy_error in + let write_data_fail _ = Lwt.fail Dummy_error in Lwt_io.with_temp_file (wrap write_data) ~prefix >>= fun _ -> let no_temps1 = not (Sys.file_exists !filename) in Lwt.catch @@ -422,7 +422,7 @@ let suite = suite "lwt_io" [ (function | Unix.Unix_error (Unix.EISDIR, "file_length", ".") -> Lwt.return_true - | exn -> Lwt.fail exn) + | exn -> Lwt.reraise exn) end; test "input channel of_bytes initial position" diff --git a/test/unix/test_lwt_unix.ml b/test/unix/test_lwt_unix.ml index b802d867da..609ca3c2a0 100644 --- a/test/unix/test_lwt_unix.ml +++ b/test/unix/test_lwt_unix.ml @@ -172,7 +172,7 @@ let utimes_tests = [ (function | Unix.Unix_error (Unix.ENOENT, "utimes", _) -> Lwt.return_unit | Unix.Unix_error (Unix.EUNKNOWNERR _, "utimes", _) -> Lwt.return_unit - | e -> Lwt.fail e) [@ocaml.warning "-4"] >>= fun () -> + | e -> Lwt.reraise e) [@ocaml.warning "-4"] >>= fun () -> Lwt.return_true); ] @@ -218,7 +218,7 @@ let readdir_tests = Lwt.return (Some filename)) (function | End_of_file -> Lwt.return_none - | exn -> Lwt.fail exn) + | exn -> Lwt.reraise exn) >>= function | None -> Lwt.return acc | Some filename -> loop (filename::acc) @@ -305,7 +305,7 @@ let readdir_tests = (function | Unix.Unix_error (Unix.EBADF, tag', _) when tag' = tag -> Lwt.return_true - | exn -> Lwt.fail exn) [@ocaml.warning "-4"] + | exn -> Lwt.reraise exn) [@ocaml.warning "-4"] in Lwt_list.for_all_s (fun (tag, t) -> expect_ebadf tag t) @@ -711,7 +711,7 @@ let writev_tests = Lwt.return_false) (function | Invalid_argument _ -> Lwt.return_true - | e -> Lwt.fail e) + | e -> Lwt.reraise e) in let close write_fd = fun () -> @@ -963,7 +963,7 @@ let bind_tests = | Unix.Unix_error (Unix.EADDRINUSE, "bind", _) | Unix.Unix_error (Unix.EISDIR, "bind", _) as exn -> if attempts <= 1 then - Lwt.fail exn + Lwt.reraise exn else bind_loop (attempts - 1) | Unix.Unix_error (Unix.EPERM, "bind", _) -> @@ -972,8 +972,8 @@ let bind_tests = test should add a check for WSL by checking for the existence of /proc/version, reading it, and checking its contents for the string "WSL". *) - Lwt.fail Skip - | e -> Lwt.fail e) [@ocaml.warning "-4"] + raise Skip + | e -> Lwt.reraise e) [@ocaml.warning "-4"] in Lwt.finalize @@ -1014,7 +1014,7 @@ let bind_tests = Lwt.return_false) (function | Unix.Unix_error (Unix.EBADF, _, _) -> Lwt.return_true - | e -> Lwt.fail e) [@ocaml.warning "-4"]); + | e -> Lwt.reraise e) [@ocaml.warning "-4"]); test "bind: aborted" (fun () -> @@ -1028,7 +1028,7 @@ let bind_tests = Lwt.return_false) (function | Exit -> Lwt.return_true - | e -> Lwt.fail e)) + | e -> Lwt.reraise e)) (fun () -> Lwt_unix.close socket)); ] diff --git a/test/unix/test_mcast.ml b/test/unix/test_mcast.ml index dbdfd446cf..a44838f552 100644 --- a/test/unix/test_mcast.ml +++ b/test/unix/test_mcast.ml @@ -26,7 +26,7 @@ let child mcast_addr join fd = if debug then Printf.printf "\nReceived multicast message %S\n%!" (Bytes.unsafe_to_string (Bytes.sub buf 0 n)); if Bytes.sub buf 0 n <> hello then - Lwt.fail (Failure "unexpected multicast message") + raise (Failure "unexpected multicast message") else Lwt.return_unit @@ -61,9 +61,9 @@ let test_mcast name join set_loop = | Unix.Unix_error (Unix.EINVAL, "send", _) | Unix.Unix_error (Unix.ENODEV, "setsockopt", _) | Unix.Unix_error (Unix.ENETUNREACH, "send", _) -> - Lwt.fail Skip + raise Skip | e -> - Lwt.fail e + Lwt.reraise e ) in Lwt.finalize t (fun () -> Lwt.join [Lwt_unix.close fd1; Lwt_unix.close fd2]) diff --git a/test/unix/test_sleep_and_timeout.ml b/test/unix/test_sleep_and_timeout.ml index 40acf13853..70831ea57e 100644 --- a/test/unix/test_sleep_and_timeout.ml +++ b/test/unix/test_sleep_and_timeout.ml @@ -35,7 +35,7 @@ let suite = suite "Lwt_unix sleep and timeout" [ | Lwt_unix.Timeout -> let check = cmp_elapsed_time "timeout" start_time duration in Lwt.return check - | exn -> Lwt.fail exn + | exn -> Lwt.reraise exn ) end; @@ -61,7 +61,7 @@ let suite = suite "Lwt_unix sleep and timeout" [ let check = cmp_elapsed_time "with_timeout : timeout" start_time duration in Lwt.return check - | exn -> Lwt.fail exn + | exn -> Lwt.reraise exn ) end;