Compare commits

...

892 Commits
v0.2 ... main

Author SHA1 Message Date
Thomas Leonard
8f7f82d2c1
Merge pull request #795 from bdodrem/windows_read-stdin__issue_793_and_792
On Windows, fix stdin broken-pipe and blocking domain.  Issues #793 and #792.
2025-01-27 12:27:06 +00:00
bdodrem
d3cb04a67e eio_windows: fix blocking bug and pipe error
* fix blocking issue on Windows : issue #793.
      Adding await_readable before reading fd

* fix broken pipe exception : issue #792.
      Use Unix.read_bigarray instead of Unix_cstruct.read

* replace eio_windows_cstruct_stubs.c by Unix functions.
     Since OCaml 5.2, Unix.read_bigarray and Unix.write_bigarray can be used.
2025-01-27 12:04:08 +00:00
Thomas Leonard
c78db1a757
Merge pull request #794 from talex5/docs
Minor documentation improvements
2025-01-16 10:51:37 +00:00
Thomas Leonard
4a19b2eea9 Minor documentation improvements
- Remove reference to Meio until it's actually working with stable
  versions of Eio.
- Remove the "Please try porting your programs" request now that Eio is
  stable.
- Use a simpler and more typical example for the switches section.
- In the `Executor_pool` make the second example create the pool the
  same way as the first one (and shrink it a bit).
- Replace out-of-date retro-httpaf-bench example with cohttp-eio and
  capnp-rpc examples.
- In `Net.run_server`, suggest using `Executor_pool`.
2025-01-15 16:59:06 +00:00
Thomas Leonard
fdd2593e33
Merge pull request #789 from talex5/fix-benchmarks
Disable dune subst
2024-12-06 20:00:33 +00:00
Thomas Leonard
c39449a631 Disable dune subst
It was always unnecessary, but with dune 3.17.0 it's causing the
benchmarks to fail to install. The error is:

    Error: There is no dune-project file in the current directory, please add one
    with a (name <name>) field in it.
    Hint: 'dune subst' must be executed from the root of the project
2024-12-06 14:54:15 +00:00
Thomas Leonard
f195295e3d
Merge pull request #787 from talex5/freebsd-close
Ignore ECONNRESET on close
2024-12-06 09:02:12 +00:00
Thomas Leonard
17665689f5 Ignore ECONNRESET on close
FreeBSD returns ECONNRESET in certain (unclear) circumstances, but it
does still close the FD successfully. If you care about this case, you
should probably use `shutdown` instead and check for it there.

Python and Ruby at least both explicitly check for and ignore this error
too.
2024-12-02 11:57:55 +00:00
Thomas Leonard
f26d70d642
Merge pull request #784 from talex5/changelog
Update changelog
2024-11-22 11:25:01 +00:00
Thomas Leonard
730033c0f1 Update changelog 2024-11-22 10:14:28 +00:00
Thomas Leonard
cc0274dd2f
Merge pull request #783 from talex5/windows-conn-aborted
eio_windows: group ECONNABORTED with other connection reset errors
2024-11-22 09:12:14 +00:00
Thomas Leonard
b971fa1cca eio_windows: group ECONNABORTED with other connection reset errors
Windows returns this if the other end disconnects (seen while testing
with both ends on the same machine).
2024-11-21 15:00:52 +00:00
Thomas Leonard
2d9e24a67f
Merge pull request #782 from talex5/openbsd
Fix tests on OpenBSD
2024-11-21 10:46:45 +00:00
Thomas Leonard
ae0eaa2dfd Fix tests on OpenBSD
- Add test dependency on bash.
- Allow www instead of http as the service name for port 80.
2024-11-21 10:27:00 +00:00
Thomas Leonard
681400c18e
Merge pull request #780 from talex5/windows-getaddrinfo
eio_windows: work around problems in Unix.getaddrinfo
2024-11-21 10:00:18 +00:00
Thomas Leonard
b41d4d8a49 eio_windows: work around problems in Unix.getaddrinfo
OCaml's `Unix.getaddrinfo` on Windows doesn't set `ai_protocol` to
anything useful, so you can't tell which addresses are TCP and which are
UDP. So, do two separate queries.
2024-11-20 12:21:04 +00:00
Thomas Leonard
9b939abedd
Merge pull request #779 from talex5/fork-bt
Preserve backtraces in fork_daemon and fork_promise_exn
2024-11-16 10:33:37 +00:00
Thomas Leonard
61f738df51 Preserve backtraces in fork_daemon and fork_promise_exn
If the fiber fails, record the backtrace when failing the switch. `fork`
itself already did this, but `fork_daemon` and `fork_promise_exn`
didn't.
2024-11-16 10:19:20 +00:00
Onah_Anthony
eb8fe3ef82
Check if windows has_symlink for tests (#771)
Symlinking is privileged operation on Windows, so we check if the running user can make symlinks before running tests that require them.
2024-10-21 15:42:36 +01:00
Thomas Leonard
debe35621e
Merge pull request #769 from patricoferris/include-fork-action-h
Make fork_action.h a public_header
2024-10-19 11:26:23 +01:00
Patrick Ferris
dd13303a4a Make fork_action.h a public_header 2024-10-17 19:43:32 +01:00
Patrick Ferris
216ccff845
Merge pull request #765 from ocaml-multicore/ai-hacking-guidance
Add advice about using AI for code generation
2024-10-07 13:22:02 +01:00
Patrick Ferris
a65fd2e612
Add advice about using AI for code generation 2024-10-06 11:28:52 +01:00
Thomas Leonard
f8c9441852
Merge pull request #756 from talex5/win-unregister-fd
eio_windows: unregister FDs on cancel
2024-09-30 12:52:39 +01:00
Thomas Leonard
8364428147 eio_windows: unregister FDs on cancel
This was fixed in cc19aa160626f5b for `eio_posix`, but not for
`eio_windows`.

The error looks like:

    exception Unix.Unix_error(Unix.ENOTSOCK, "select", "")
2024-09-29 14:15:13 +01:00
Thomas Leonard
d2a3e21bc4
Merge pull request #755 from talex5/cleanup
Minor code cleanups
2024-09-18 16:45:45 +01:00
Thomas Leonard
424f5e8c96 Move Dla to eio.utils
It doesn't need to be in eio.core, as nothing else there depends on it.
2024-09-18 12:02:49 +01:00
Thomas Leonard
89019ddd3d Remove unused library
eio.core doesn't use cstruct.
2024-09-18 11:51:57 +01:00
Thomas Leonard
534f89cf0e
Merge pull request #754 from talex5/quiet-tsan
eio_linux: avoid triggering a TSan warning
2024-09-07 14:06:38 +01:00
Thomas Leonard
9ca440ac5c eio_linux: avoid triggering a TSan warning
TSan warns that setting `statx_works` races with users of it. This isn't
really a problem, because we always set it to the same value, but it's
distracting when looking for other bugs.

Reported by Anil Madhavapeddy in #751.
2024-09-06 17:23:32 +01:00
Thomas Leonard
e2dc1d79e5
Merge pull request #753 from talex5/cancel-alloc-fixed
eio_linux: allow alloc_fixed_or_wait to be cancelled
2024-09-06 17:23:14 +01:00
Thomas Leonard
0c414327d1 eio_linux: allow alloc_fixed_or_wait to be cancelled 2024-09-06 17:11:37 +01:00
Thomas Leonard
0f6b65dea2
Merge pull request #752 from talex5/linux-get-sched
eio_linux: refactor fixed buffer code
2024-09-05 11:07:08 +01:00
Thomas Leonard
cc2cd3da32 eio_linux: refactor fixed buffer code
Instead of having separate Alloc, Alloc_or_wait and Free effects,
the scheduler now provides a single Get effect to return itself,
and the actual work is now done in the calling fiber. This is cleaner,
and seems to be slightly faster too.

Note that `alloc_fixed_or_wait` is currently not cancellable (it wasn't
before either, but it's more obvious now).

It would be possible to use DLS to store the scheduler rather than using
an effect. However, the improvement in speed is minimal and there are
some complications with sys-threads, so probably better to wait for
OCaml to support thread-local-storage first.
2024-09-04 15:41:36 +01:00
Thomas Leonard
d47b5e29f7
Merge pull request #749 from talex5/trace-spawn
Record trace event when spawning processes
2024-08-23 15:16:55 +01:00
Thomas Leonard
c2d314b930 Record trace event when spawning processes
This can take quite a long time.
2024-08-23 15:03:43 +01:00
Thomas Leonard
33d4e01a30
Merge pull request #745 from talex5/fs-example
examples/fs: show how to read files while scanning
2024-06-28 11:03:33 +01:00
Thomas Leonard
c45978252a examples/fs: show how to read files while scanning 2024-06-22 16:41:21 +01:00
Thomas Leonard
37840760b1
Merge pull request #744 from talex5/unix-net-types
Eio_unix.Net: make some return types more polymorphic
2024-06-21 10:17:40 +01:00
Thomas Leonard
8cb86a703e Eio_unix.Net: make some return types more polymorphic
This allows e.g. using the result of the `import_socket_stream` as a
`stream_socket_ty` without needing a cast.
2024-06-20 09:37:40 +01:00
Thomas Leonard
12721820e4
Merge pull request #743 from talex5/win-fwd-slash
Eio.Path: always use "/" as separator
2024-06-19 17:13:42 +01:00
Thomas Leonard
32e501a198 Eio.Path: always use "/" as separator
path.mli says:

> In Eio, the directory separator is always "/", even on Windows.

However, `Path.(/)` used `Filename.concat` to create paths, which uses
the native separator ("\" on Windows).
2024-06-19 14:27:26 +01:00
Thomas Leonard
a21b507de8
Merge pull request #742 from talex5/win-openat-debug
eio_windows: improve openat error handling
2024-06-19 14:12:20 +01:00
Thomas Leonard
54df8fdd62 eio_windows: run the fs example in CI 2024-06-19 11:53:18 +01:00
Thomas Leonard
2cd09925d0 eio_windows: improve openat error handling 2024-06-19 11:45:19 +01:00
Thomas Leonard
642bdbef7e
Merge pull request #741 from copy/main
define struct clone_args for linux-lts versions that don't have it
2024-06-19 11:26:07 +01:00
Fabian
574afc103a define struct clone_args for linux-lts versions that don't have it 2024-06-18 19:12:52 +01:00
Thomas Leonard
17562f24fe
Merge pull request #739 from talex5/doc-seq
Add example to `Buf_read.seq` documentation
2024-06-17 09:27:07 +01:00
Thomas Leonard
8ef76f06bf Add example to Buf_read.seq documentation 2024-06-14 11:30:09 +01:00
Thomas Leonard
77d881014d
Merge pull request #735 from talex5/release
Prepare release
2024-05-28 12:06:37 +01:00
Thomas Leonard
e32331a835 Prepare release 2024-05-28 11:58:43 +01:00
Thomas Leonard
3e67f7d4cb
Merge pull request #733 from alyssais/listening
Add Eio_unix.Net.import_socket_listening
2024-05-28 11:57:08 +01:00
Alyssa Ross
3b2a67966e Add Eio_unix.Net.import_socket_listening 2024-05-28 10:41:45 +01:00
Thomas Leonard
2c5eb612db
Merge pull request #734 from talex5/fix-signal-race
eio_linux: add work-around for signals race
2024-05-26 14:38:25 +01:00
Thomas Leonard
ab12b0b77c eio_linux: add work-around for signals race
This is a quick fix for https://github.com/ocaml-multicore/eio/issues/732.
2024-05-23 12:12:46 +01:00
Thomas Leonard
c023b2e750
Merge pull request #731 from talex5/release
Prepare release
2024-05-21 15:59:51 +01:00
Thomas Leonard
687017078a Prepare release 2024-05-21 14:56:35 +01:00
Thomas Leonard
2146c8a181
Merge pull request #730 from talex5/fs-example
Add examples/fs showing how to walk a directory tree
2024-05-21 12:19:38 +01:00
Thomas Leonard
7d718405a6 Add examples/fs showing how to walk a directory tree 2024-05-21 11:20:26 +01:00
Thomas Leonard
d834d7391f
Merge pull request #729 from talex5/uring-submit
eio_linux: don't record submit events when there's nothing to submit
2024-05-21 11:07:56 +01:00
Thomas Leonard
d26184dbf7 eio_linux: don't record submit events when there's nothing to submit 2024-05-20 16:53:10 +01:00
Thomas Leonard
73f913c108
Merge pull request #728 from talex5/linux-skip-submit
eio_linux: don't call submit immediately before wait
2024-05-15 09:36:13 +01:00
Thomas Leonard
62c1925dfe eio_linux: don't call submit immediately before wait
It's quicker to do it in a single call.
2024-05-14 12:56:59 +01:00
Thomas Leonard
b126756eb8
Merge pull request #727 from talex5/split-linux
eio_linux: split flow into its own file
2024-05-11 18:25:48 +01:00
Thomas Leonard
3c93b4405c eio_linux: split flow into its own file 2024-05-10 10:00:18 +01:00
Thomas Leonard
bd2c92e7ba
Merge pull request #726 from talex5/timeout-sleep
Add Timeout.sleep
2024-05-02 14:37:46 +01:00
Thomas Leonard
a0cb744256 Add Timeout.sleep
This makes it easier to use timeouts as simple delays.
2024-05-01 14:33:46 +01:00
Thomas Leonard
49c9774ee3
Merge pull request #715 from patricoferris/symlinks
Add symlink support
2024-04-28 11:18:20 +01:00
Patrick Ferris
d3f30696c2 Add symlink support 2024-04-25 14:40:26 +01:00
Thomas Leonard
c1c2d634de
Merge pull request #723 from jebrosen/fix/openbsd
eio_posix: fix filesystem tests on OpenBSD
2024-04-05 11:23:00 +01:00
Jeb Rosen
7c9396ef19 eio_posix: check for ELOOP in low-level fs 'resolve'
In the 'resolve' function there is a check for items being symlinks
which was missing ELOOP as an indicator. This patch adds ELOOP, to match
the similar check in 'open_beneath_fallback'.

This fixes filesystem tests on OpenBSD.
2024-04-04 17:06:29 -04:00
Thomas Leonard
58aa3f6663
Merge pull request #722 from prgbln/fix_openbsd
Add _BSD_SOURCE flag to fix eio_posix on OpenBSD.
2024-04-04 16:58:26 +01:00
prgbln
b8a99fa036 Add _BSD_SOURCE flag to fix eio_posix on OpenBSD. 2024-04-04 12:53:55 +02:00
Thomas Leonard
b128edce66
Merge pull request #718 from lucperkins/mdash-in-readme-title
Long dash in README title
2024-03-29 11:27:24 +00:00
Thomas Leonard
3be614e86f
Merge pull request #657 from SGrondin/pool-never-block
Add Eio.Pool.use ~never_block
2024-03-26 09:57:03 +00:00
Simon Grondin
321bc093b4 Add Eio.Pool.use ~never_block 2024-03-25 14:33:51 +00:00
Thomas Leonard
1776925870
Merge pull request #719 from talex5/bench-info
Benchmarks: record uname, Eio backend, and number of cores
2024-03-25 09:46:45 +00:00
Thomas Leonard
94ab6cb65f Benchmarks: record uname, Eio backend, and number of cores 2024-03-25 09:24:22 +00:00
Thomas Leonard
c53d897cbd
Merge pull request #720 from talex5/fix-isatty
eio_linux: require Linux >= 5.15
2024-03-24 16:52:50 +00:00
Thomas Leonard
f2ce0c26ed eio_linux: require Linux >= 5.15
This allows removing a work-around that required checking whether every
flow was a tty (and which was doing this on every read).
2024-03-24 09:27:13 +00:00
Luc Perkins
7608cbaa60
Use long dash in README title 2024-03-23 09:31:10 -03:00
Thomas Leonard
911ccc8f6a
Merge pull request #717 from talex5/read-all-hint
README: explain that read_all reads until shutdown
2024-03-23 11:13:48 +00:00
Thomas Leonard
12530ebeca README: explain that read_all reads until shutdown 2024-03-23 10:57:57 +00:00
Thomas Leonard
14ae3cfee3
Merge pull request #712 from talex5/update-mdx
Update to MDX 2.4.1
2024-03-15 11:29:21 +00:00
Thomas Leonard
6ae124ecc5 Update to MDX 2.4.1 2024-03-14 11:53:04 +00:00
Thomas Leonard
34b650bd47
Merge pull request #711 from talex5/docs-1.0
Update README for Eio 1.0
2024-03-14 11:10:39 +00:00
Thomas Leonard
240e04f761 Update README for Eio 1.0
- Remove the Status section.
- Mention `Eio_js`.
- Simplify the hello-world example.
- Mention `Eio_unix.sleep` in the time section.
- Mention non-determinism only applies to the main Eio API.
2024-03-13 12:00:27 +00:00
Thomas Leonard
15f2047323
Merge pull request #710 from talex5/mdx
Bound MDX version on all packages
2024-03-11 12:48:57 +00:00
Thomas Leonard
9bbd4e0108 Bound MDX version on all packages 2024-03-10 15:28:05 +00:00
Thomas Leonard
edfe8debb2
Merge pull request #709 from talex5/release
Prepare release
2024-03-10 15:10:31 +00:00
Thomas Leonard
f3f24738c4 Prepare release 2024-03-10 12:51:56 +00:00
Thomas Leonard
ce30c9a270
Merge pull request #707 from talex5/executor-pool-docs
Executor pool docs
2024-03-10 12:38:31 +00:00
Simon Grondin
feb8d11d91 Add README documentation for Eio.Executor_pool 2024-03-10 12:32:35 +00:00
Thomas Leonard
82dcca77bf Add multicore trace illustration to README 2024-03-10 11:51:47 +00:00
Thomas Leonard
ed9c4a5574
Merge pull request #708 from talex5/nologs
eio_linux: remove logging
2024-03-10 11:51:33 +00:00
Thomas Leonard
ebf7fa0230 eio_linux: remove logging
There were only two remaining uses, neither of which has proved useful.
The warning about an unknown response to cancellation has never been
seen, and the warning about being unable to allocate a fixed buffer just
annoys people.
2024-03-08 17:51:12 +00:00
Thomas Leonard
5e6618168c
Merge pull request #705 from talex5/linux-ll
eio_linux: expose more functions in the Low_level module
2024-02-29 16:19:07 +00:00
Thomas Leonard
fb731b5161 eio_linux: move Low_level signature to low_level.mli
This ensures that the low-level API provided to users is the same one
that is used internally, so we can't forget to expose things in future.
2024-02-29 16:09:34 +00:00
Thomas Leonard
e48f6114b1 eio_linux: Expose more functions in Low_level module
Add all the functions used by other parts of eio_linux (`openat`,
`mkdir`, `read_link`, `unlink`, `rename` and `pipe`).

Tidied the API up a bit too:
- `mkdir_beneath` is now just `mkdir`.
- `statx_confined` is now just `statx`.
- `open_dir` is gone; the single user now calls `openat` directly.
2024-02-29 16:09:34 +00:00
Thomas Leonard
7b58999f51
Merge pull request #706 from talex5/mdx-fail
Require MDX < 2.4.0
2024-02-29 16:08:21 +00:00
Thomas Leonard
6309314933 Require MDX < 2.4.0
The new version attempts to execute included blocks, and also seems
broken on macos.
2024-02-29 16:01:05 +00:00
Thomas Leonard
ef415fbdfe
Merge pull request #704 from ocaml-multicore/fix-to-pass-with-old-and-new-kcas
Fix to pass with both old and new Kcas
2024-02-26 17:04:37 +00:00
Thomas Leonard
31ee72e25a
Merge pull request #697 from talex5/cap-enter
Add Eio_unix.Cap module to enable Capsicum mode
2024-02-26 17:03:40 +00:00
Thomas Leonard
ca121eea64 Add Eio_unix.Cap module to enable Capsicum mode 2024-02-26 11:43:13 +00:00
Vesa Karvonen
6ee8ab97b8 Fix to pass with both old and new Kcas
The `Loc.t` type has changed, which makes a MDX block fail.  This changes the
block such that it will pass with both old and new Kcas.
2024-02-25 23:49:22 +02:00
Thomas Leonard
261b583c5b
Merge pull request #703 from SGrondin/idempotent-test
Make posix open_beneath test idempotent
2024-02-25 19:17:01 +00:00
Simon Grondin
ec576100ef Make posix open_beneath test idempotent 2024-02-25 11:16:55 -06:00
Thomas Leonard
57c02073ee
Merge pull request #702 from talex5/execpool-err
Executor_pool: mention requested weight in error message
2024-02-23 17:20:22 +00:00
Thomas Leonard
e5444c0f8b Executor_pool: mention requested weight in error message
Spotted by Yawar Amin.
2024-02-23 14:06:04 +00:00
Thomas Leonard
4f3ec08ef5
Merge pull request #701 from talex5/release
Update changelog
2024-02-22 14:21:35 +00:00
Thomas Leonard
332adbd514 Update changelog 2024-02-22 13:41:01 +00:00
Thomas Leonard
d33f8c2d57
Merge pull request #699 from talex5/eio-trace-render
Update build for new eio-trace CLI
2024-02-22 11:14:31 +00:00
Thomas Leonard
02fbb947e8 Update build for new eio-trace CLI 2024-02-21 14:34:09 +00:00
Thomas Leonard
6dcc452a7e
Merge pull request #698 from talex5/docs
Add more trace diagrams to README
2024-02-21 12:53:28 +00:00
Thomas Leonard
c2f22d85eb Illustrate network example 2024-02-21 10:48:58 +00:00
Thomas Leonard
1cbcda0ce1 Illustrate switch example 2024-02-21 10:27:59 +00:00
Thomas Leonard
59814f61b1 Remove out-of-date performance section from README
It relies on the `splice` optimisation, which is currently disabled, and
encourages `Flow.copy`, which is often a bad choice as you can't control
the buffer size.
2024-02-21 10:02:02 +00:00
Thomas Leonard
c3a43003bc
Merge pull request #696 from talex5/macos-slash
eio_posix: add O_DIRECTORY when opening with a trailing slash
2024-02-21 10:00:29 +00:00
Thomas Leonard
a4bd5d79c4 eio_posix: add O_DIRECTORY when opening with a trailing slash
Most systems do this anyway, but macos doesn't seem to. This makes
things consistent.
2024-02-19 15:49:44 +00:00
Thomas Leonard
b9a0c20c2c
Merge pull request #694 from talex5/posix-dirfd
eio_posix: use directory FDs instead of realpath
2024-02-19 15:35:57 +00:00
Thomas Leonard
301b479fa6 eio_posix: use directory FDs instead of realpath
realpath was an old hack from the libuv days.
2024-02-19 13:01:21 +00:00
Thomas Leonard
f9ba4caf3d
Merge pull request #693 from talex5/resolve-retry
eio_linux: retry openat2 on EAGAIN
2024-02-15 14:20:51 +00:00
Thomas Leonard
3adce92e8a eio_linux: retry openat2 on EAGAIN
CI fails sometimes with this error. The man-page says:

> the kernel could not ensure that a ".." component didn't escape (due
> to a race condition or potential attack). The caller may choose to
> retry the openat2() call.
2024-02-15 09:49:41 +00:00
Thomas Leonard
f5232a0c17
Merge pull request #692 from talex5/stat-bench
Stat benchmark: report cleanup time and optimise
2024-02-14 16:09:45 +00:00
Thomas Leonard
db8672b737 Optimise stat benchmark
- Instead of creating thousands of fibers and having them fight over a
  semaphore, limit the number of fibers created. This also makes the
  traces easier to view.

- Remove the items in parallel.

- Fill the files with zero bytes instead of asking the OS for secure
  random data, since that's slow and isn't useful for the test.
2024-02-14 12:13:07 +00:00
Thomas Leonard
250affff67 Stat benchmark: report cleanup time too 2024-02-14 11:40:13 +00:00
Thomas Leonard
43bd3b852e
Merge pull request #691 from talex5/uring-eperm
Handle EPERM when trying to initialise uring
2024-02-14 10:01:52 +00:00
Thomas Leonard
7b781b56b6 Handle EPERM when trying to initialise uring
This can happen when using a Docker container.
2024-02-14 09:42:49 +00:00
Thomas Leonard
82c47a25b2
Merge pull request #690 from talex5/fd-bits
Minor file-descriptor improvements
2024-02-14 09:40:05 +00:00
Thomas Leonard
91ca880220 Add test for Path.open_dir 2024-02-14 09:16:35 +00:00
Thomas Leonard
2f6a5790ba Add Eio_unix.Fd.is_open convenience function 2024-02-13 16:35:43 +00:00
Thomas Leonard
2c005eddaf Enable O_PATH on Linux
Requires `-D_GNU_SOURCE` for some reason.
2024-02-13 16:35:43 +00:00
Thomas Leonard
666c5236d7
Merge pull request #689 from talex5/lintcstubs
Enable lintcstubs for Eio_unix.Private too
2024-02-13 09:08:04 +00:00
Thomas Leonard
6b5a5366c6 Enable lintcstubs for Eio_unix.Private too 2024-02-12 17:37:14 +00:00
Thomas Leonard
717af4433e
Merge pull request #688 from talex5/systhread-bt
Include backtrace in systhread errors
2024-02-12 08:50:28 +00:00
Thomas Leonard
bf7348497c Include backtrace in systhread errors
Also, add `Exn.empty_backtrace` as a convenience.
2024-02-11 16:48:55 +00:00
Thomas Leonard
ca4767bc96
Merge pull request #681 from talex5/pool-systhreads
Keep pool of systhreads for blocking operations (simplified)
2024-02-11 10:04:11 +00:00
Thomas Leonard
17b976f37c Thread pool simplifications
- Add `Run_in_systhread` effect. This makes the thread-pool part of the
  scheduler's state, avoiding the need for DLS.

- Remove `max_standby_systhreads_per_domain`. Suggested by Vesa Karvonen.

- Simplify termination. Use one atomic instead of two.

- Use the scheduler's timer to drop idle threads.
  Modified Zzz to allow non-fiber timeouts.
2024-02-09 11:36:28 +00:00
Simon Grondin
77c111814f Keep pool of systhreads for blocking operations 2024-02-09 11:36:28 +00:00
Thomas Leonard
e9864b96fb
Merge pull request #684 from talex5/threadsafe-resources
Make Switch.on_release thread safe
2024-02-09 11:33:42 +00:00
Thomas Leonard
d979cc09fc Make Switch.on_release thread safe
This is needed to allow resource pools to be shared between domains.
2024-02-09 09:49:04 +00:00
Thomas Leonard
a9e552fafe
Merge pull request #686 from talex5/readlink
Add Eio.Path.read_link
2024-02-09 09:26:22 +00:00
Thomas Leonard
11ab66c2e7 Add Eio.Path.read_link 2024-02-09 09:13:44 +00:00
Thomas Leonard
299c033a9a
Merge pull request #687 from talex5/trace-mock-backend
eio.mock: add tracing support
2024-02-09 09:12:55 +00:00
Thomas Leonard
14f7dddf44 eio.mock: add tracing support
Report trace events when switching fibers so that tests using the mock
backend can be traced too.
2024-02-08 14:50:21 +00:00
Sudha Parimala
913b501bd7
Merge pull request #685 from ocaml-multicore/adjust-coc-contacts
Adjust COC contacts
2024-02-07 11:24:02 +05:30
Vesa Karvonen
017bfd8c03 Adjust COC contacts 2024-02-06 19:24:07 +02:00
Thomas Leonard
b1808e6656
Merge pull request #683 from talex5/trace-cancel
Trace cancellation
2024-02-05 20:50:13 +00:00
Thomas Leonard
d42bf6446c Trace cancellation
Record when a context is cancelled.
2024-02-05 15:53:13 +00:00
Thomas Leonard
92a21c6ce5
Merge pull request #682 from talex5/verification
Link to verification work in docs
2024-02-05 14:56:07 +00:00
Thomas Leonard
e433e7498e Link to verification work in docs 2024-02-05 14:54:47 +00:00
Thomas Leonard
964ed27305
Merge pull request #678 from talex5/bench_systhreads
Benchmark Eio_unix.run_in_systhread
2024-02-01 09:51:00 +00:00
Thomas Leonard
07878b7f59 Benchmark Eio_unix.run_in_systhread 2024-01-31 16:08:47 +00:00
Thomas Leonard
87d6a990af
Merge pull request #676 from talex5/trace-domains
Trace domain spawning
2024-01-31 14:22:41 +00:00
Thomas Leonard
5052181006 Trace domain spawning 2024-01-31 12:16:39 +00:00
Thomas Leonard
6dcc5e2027
Merge pull request #674 from talex5/starvation
eio_posix and eio_windows: check for IO periodically
2024-01-30 10:57:48 +00:00
Thomas Leonard
c4cb2737d5 Don't exit scheduler if run-queue isn't empty
This probably couldn't happen anyway, since `main` counts towards
`active_ops`, but it's clearer to check.
2024-01-30 10:42:07 +00:00
Thomas Leonard
e48c5e9b81 eio_posix and eio_windows: check for IO periodically
These already had code to check periodically for timeouts, but forgot to
check for IO at the same time.
2024-01-30 09:58:49 +00:00
Thomas Leonard
0776a7d60a
Merge pull request #675 from talex5/declutter-trace
Improve traces
2024-01-27 16:29:37 +00:00
Thomas Leonard
4a91a679e1 Update tracing section of README 2024-01-27 16:02:27 +00:00
Thomas Leonard
d9a7e20a2b Trace Eio_unix.run_in_systhread 2024-01-26 17:05:53 +00:00
Thomas Leonard
41e6e46cf4 Trace Unix.close, submit and traceln 2024-01-26 16:00:10 +00:00
Thomas Leonard
80dd943f80 Clean up trace for finishing switches
Instead of waiting for fibers to finish in a protected block, use a
non-cancellable wait. This simplifies the traces in some cases.
2024-01-26 16:00:10 +00:00
Thomas Leonard
b68d396787
Merge pull request #673 from talex5/bench-init
Make benchmarks start faster
2024-01-25 15:30:42 +00:00
Thomas Leonard
5497bdda48 Make benchmarks start faster
The Buf_read benchmark created a large string at start-up using
`String.init`, which adds about 150ms to the start-up time of all
benchmarks. Now, only create the string when running that benchmark, and
use the more efficient `String.make`.
2024-01-25 13:38:14 +00:00
Thomas Leonard
da4f6c3978
Merge pull request #672 from talex5/macos-ci
Expect opam-repo-ci tests to fail on macos
2024-01-19 10:28:19 +00:00
Raphaël Proust
1c8c759f09 Expect opam-repo-ci tests to fail on macos
It uses a sandbox that causes the network tests to fail, even though
they only use the loopback interface.
2024-01-19 09:50:14 +00:00
Thomas Leonard
c502574915
Merge pull request #671 from talex5/doc-fixes
Update changelog
2024-01-17 13:29:23 +00:00
Thomas Leonard
f4386bb5bc Update changelog 2024-01-17 13:28:10 +00:00
Thomas Leonard
ffe4920127
Merge pull request #670 from talex5/doc-fixes
Minor documentation updates
2024-01-17 12:13:53 +00:00
Simon Grondin
09c282d1e8 Minor documentation updates
- OCaml 5.1 is now the minimum version
- Add some helpful links
- Clarify executor pool API docs
2024-01-17 11:38:45 +00:00
Thomas Leonard
ccf1ba770d
Merge pull request #555 from mefyl/main
Add `listening_socket#listening_addr`
2024-01-15 10:03:05 +00:00
mefyl
122c3b1be9 Add Net.listening_addr 2024-01-15 09:44:27 +00:00
Thomas Leonard
71faca6414
Merge pull request #667 from talex5/docs
Organise eio.mli better
2024-01-13 11:15:06 +00:00
Thomas Leonard
f06f4a4ff2 Organise eio.mli better
Group modules so that odoc can generate a useful index.
2024-01-11 14:59:29 +00:00
Thomas Leonard
464a5623b0
Merge pull request #655 from SGrondin/buf-write-printf-mut
Add Buf_write.printf (custom formatter)
2024-01-08 13:36:00 +00:00
Simon Grondin
8d6e6bab83 Add Buf_write.printf
Co-authored-by: Thomas Leonard <talex5@gmail.com>
2024-01-08 13:13:21 +00:00
Thomas Leonard
cfafb22fb6
Merge pull request #666 from talex5/quote-args
Fix quoting of quotes in process error messages
2024-01-08 13:00:25 +00:00
Thomas Leonard
81bce87e3f Fix quoting of quotes in process error messages
When displaying an argument with quotes, they should be escaped to avoid
ambiguity (`Fmt.quote` doesn't do this; it just puts quotes around the
outside of the string).
2024-01-05 09:59:07 +00:00
Thomas Leonard
4856fc430d
Merge pull request #665 from talex5/all-shared
Fiber.all: use the parent fiber
2024-01-04 08:57:54 +00:00
Thomas Leonard
2aaea3f48e Fiber.all: use the parent fiber
Previously, it created one fiber for each job and then the parent
fiber just waited. It's a little more efficient to have the parent
fiber run the last job itself, and also reduces clutter in the traces.

This also affects `Fiber.both`, which uses `all` internally.
2024-01-03 12:52:03 +00:00
Thomas Leonard
d338f8bc61
Merge pull request #587 from SGrondin/fiber-races
Safe Fiber races: `~combine` and `Fiber.n_any`
2024-01-03 11:00:30 +00:00
Simon Grondin
cbb6ece7b2 Safe Fiber races: ~combine and n_any
Co-authored-by: Thomas Leonard <talex5@gmail.com>
2024-01-03 10:43:42 +00:00
Thomas Leonard
c9db164b8b
Merge pull request #664 from talex5/fail-bt
Remove default backtrace from Switch.fail
2024-01-03 09:59:58 +00:00
Thomas Leonard
c42de1e493 Remove default backtrace from Switch.fail
If you didn't pass a backtrace then it would attach a bogus one,
which was confusing.
2024-01-02 16:31:54 +00:00
Thomas Leonard
b942dde3b3
Merge pull request #663 from talex5/flow-buf
Optimise Flow.copy with Buf_read.as_flow
2024-01-02 10:29:15 +00:00
Thomas Leonard
19c43d7153 Optimise Flow.copy with Buf_read.as_flow
By default, Flow.copy creates a 4KB buffer and copies the data through
that. However, if the source of the copy is a buffered reader then it is
much more efficient to use its buffer directly.

This updates the flow you get from `Buf_read.as_flow` to offer this
optimisation, and also updates the eio_posix backend's flow to use this
optimisation when available (eio_linux already supported this).

Detected in a benchmark by Leandro Ostera.
2024-01-02 10:20:42 +00:00
Thomas Leonard
fbe8a71cb8 Add Flow.copy benchmark 2024-01-02 10:15:57 +00:00
Thomas Leonard
ca1d239507
Merge pull request #662 from SGrondin/idempotent-tests2
Fix non-idempotent tests
2023-12-31 17:34:56 +00:00
Simon Grondin
c6807267d2 Fix non-idempotent tests 2023-12-22 13:20:37 -06:00
Thomas Leonard
5e014fcd17
Merge pull request #661 from talex5/trace-switch
Tracing: add labels to switches
2023-12-22 11:03:51 +00:00
Thomas Leonard
c88c58e835 Tracing: add labels to switches
This makes the traces easier to read.
2023-12-21 16:20:32 +00:00
Thomas Leonard
4db993d8d6
Merge pull request #656 from talex5/trace-cc
Improve tracing
2023-12-20 09:32:30 +00:00
Thomas Leonard
69b115054c Improve tracing
This adds tracing of cancellation contexts and OS operations, and
documents and cleans up the API a bit.

When reading events, instead of the consumer providing one function per
event, we now use a single function taking a variant. This makes the API
easier to use and lets the caller decide whether they want to handle all
events (checked by the compiler) or just some of them.

Note: eio_posix yields before each operation to avoid starvation.
However, if it needed to block then it did another yield afterwards when
retrying, which isn't necessary and clutters up the traces. This commit
also removes that.

Co-authored-by: Lucas Pluvinage <lucas@tarides.com>
2023-12-19 15:26:47 +00:00
Thomas Leonard
21e05a834a
Merge pull request #659 from talex5/fs-docs
Mention Path module in File and Fs documentation
2023-12-18 14:46:33 +00:00
Thomas Leonard
0751f02e6b Mention Path module in File and Fs documentation
Someone trying to open a file may start looking in these modules.
Explain what each module is for and point people at the Path module.
2023-12-18 11:29:53 +00:00
Thomas Leonard
a26c3ebccc
Merge pull request #654 from talex5/posix-blocking
eio_posix: use caml_enter_blocking_section in more places
2023-12-06 10:35:19 +00:00
Thomas Leonard
538d3840c5 eio_posix: use caml_enter_blocking_section in more places
`readv`, `writev`, `preadv` and `pwritev` didn't release the lock during
syscalls.
2023-12-04 11:16:04 +00:00
Thomas Leonard
d2410e51d5
Merge pull request #653 from talex5/iomax
Fix handling of very long IO vectors
2023-12-04 11:05:54 +00:00
Thomas Leonard
b388d67aa4 Fix handling of very long IO vectors
`Flow.write` doesn't place any limit on how long the list of vectors can
be, but real operating systems do have limits and will fail if given too
many.

Also, Eio_posix was allocating the array on the stack, which could fail
if it was very large.
2023-12-04 09:53:38 +00:00
Thomas Leonard
211279e8dc
Merge pull request #651 from talex5/fix-gc-hang
eio_posix: work around caml_unix_alloc_sockaddr bug
2023-12-03 11:45:26 +00:00
Thomas Leonard
997f34949f eio_posix: work around caml_unix_alloc_sockaddr bug
If given an empty address, it would corrupt memory.
This resulted in the tests hanging sometimes, spinning in
`caml_do_local_roots`.

See https://github.com/ocaml/ocaml/issues/12796.
2023-12-01 14:07:09 +00:00
Thomas Leonard
ffe69561d2
Merge pull request #639 from SGrondin/executorpool
Add Eio.Executor_pool
2023-11-23 10:05:50 +00:00
Thomas Leonard
efad0145a1 Ensure executor pool always resolves promised when cancelled
If a worker was cancelled just as it accepted a job, the fork would fail
and the promise would remain unresolved.
2023-11-23 09:46:09 +00:00
Simon Grondin
fc7c541d7d Add Eio.Executor_pool 2023-11-22 08:30:10 -06:00
Thomas Leonard
8a71a90c9a
Merge pull request #647 from talex5/new-lfqueue
Faster and simpler Lf_queue
2023-11-19 10:18:01 +00:00
Thomas Leonard
5ab8302ce7 Faster and simpler Lf_queue
The old code was based on a multi-consumer design, but specialised to a
single consumer. As Vesa Karvonen points out, if there is only a single
consumer then a more efficient design is possible.

Previously, adding an item required an atomic read and two CAS
operations (in the uncontended case). It now requires an atomic read and
one CAS. Taking an item previously required an atomic read in all cases,
but now often requires no atomic operations. `push_head` no longer
requires any atomic operations.

This also removes all the uses of `Obj.magic` from the module.
2023-11-17 10:07:18 +00:00
Thomas Leonard
bdc42827c5
Merge pull request #646 from talex5/try-resolve
Add Promise.try_resolve
2023-11-16 15:02:34 +00:00
Thomas Leonard
cd6f4dfee6 Add Promise.try_resolve 2023-11-16 11:37:12 +00:00
Thomas Leonard
1d45280f96
Merge pull request #644 from talex5/mock-clock
eio.mock: auto-advancing mock clock
2023-11-15 19:01:17 +00:00
Thomas Leonard
eb64c46f85
Merge pull request #643 from talex5/lint
eio_windows: add explicit fmt dependency
2023-11-15 12:27:44 +00:00
Thomas Leonard
fc930ca77e eio.mock: add mock clock to environment
The clock advances automatically when the run queue is empty.
2023-11-15 12:15:26 +00:00
Thomas Leonard
da975f06bf eio.mock: add Backend.run_full
This is mainly to allow adding other mock resources here in future.
2023-11-15 11:52:22 +00:00
Thomas Leonard
c286d1047d eio_windows: add explicit fmt dependency
Avoids a CI warning.
2023-11-15 10:25:16 +00:00
Thomas Leonard
cea0546c4c
Merge pull request #641 from talex5/stream-close
Allow closing synchronous streams
2023-11-13 12:50:38 +00:00
Thomas Leonard
c7c2f72e6f Allow closing synchronous streams
This isn't currently exposed in the public interface.
2023-11-13 12:36:56 +00:00
Thomas Leonard
9537ca1810
Merge pull request #640 from talex5/cancel-exn
Remove Cancel_hook_failed
2023-11-09 19:57:37 +00:00
Thomas Leonard
cf4da1a369 Remove Cancel_hook_failed
It's not used for anything and it breaks dscheck.
2023-11-08 13:55:56 +00:00
Thomas Leonard
bc1e231b64
Merge pull request #638 from talex5/centos-7
Don't use uring on centos 7
2023-11-02 10:58:56 +00:00
Thomas Leonard
d618731781 Don't use uring on centos 7
The C headers there are too old even to compile it.
2023-11-02 10:40:34 +00:00
Thomas Leonard
00bcf7c79c
Merge pull request #637 from talex5/changes
Updated CHANGES.md
2023-11-01 16:35:47 +00:00
Thomas Leonard
3c8b2c81df Updated CHANGES.md 2023-11-01 15:49:06 +00:00
Thomas Leonard
6e6ae312e8
Merge pull request #635 from talex5/trace
Switch from CTF to OCaml 5.1 runtime events
2023-10-26 15:07:15 +01:00
Thomas Leonard
c721a076c7 Switch from CTF to custom runtime events
This is a minimal initial version, which mostly keeps the same set of
events as before.

The new `eio.runtime_events` library defines the events and their types.
`lib_eio/core/trace.ml` now uses this instead of using CTF.

`examples/trace/main.ml` shows how to subscribe to events.

One slight difference is that `current_thread` has gone. It is now up to
the reader to remember which thread was active. In a few places this
meant recording e.g. reading a promise at the point where the fiber is
resumed, not when it is enqueued.

`Ctx_unix` is gone. Eio tracing is automatically enabled whenever OCaml
tracing is.

Co-authored-by: Thomas Leonard <talex5@gmail.com>
Co-authored-by: Patrick Ferris <patrick@sirref.org>
2023-10-25 12:47:23 +01:00
Thomas Leonard
9e1872c10c
Merge pull request #634 from talex5/rename-ctf
Rename Ctf to Trace and tidy
2023-10-24 11:36:26 +01:00
Thomas Leonard
ddcf89983d Rename trace functions
e.g. `Ctf.note_read` becomes `Trace.read`.

Also, remove some unused functions.
2023-10-23 11:57:47 +01:00
Thomas Leonard
5a11ea6df2 Rename Ctf to Trace
This is to minimise the diff when switching to runtime events.
2023-10-23 11:23:09 +01:00
Thomas Leonard
60e67472e3
Merge pull request #632 from talex5/changes-ws
Allow trailing whitespace in CHANGES.md
2023-10-13 14:20:48 +01:00
Thomas Leonard
e96c444d66 Allow trailing whitespace in CHANGES.md
This isn't an error; markdown uses it for a line-break.
2023-10-13 13:27:34 +01:00
Thomas Leonard
ee51b04408
Merge pull request #596 from SGrondin/add-flow-load
Add Flow.read_all
2023-10-11 10:26:37 +01:00
Thomas Leonard
c6067a9059 Improve odoc for read_all 2023-10-11 09:54:23 +01:00
Simon Grondin
8da6a7d7f1 Add Flow.read_all 2023-10-11 09:54:19 +01:00
Thomas Leonard
669c2e86c0
Merge pull request #630 from talex5/bench-stat
Add Path.stat benchmark
2023-10-09 17:49:40 +01:00
Patrick Ferris
121f5051ab Add Path.stat benchmark
Co-authored-by: Thomas Leonard <talex5@gmail.com>
2023-10-09 14:46:01 +01:00
Thomas Leonard
434fb81e14
Merge pull request #631 from talex5/ocaml-5.1
Update minimum OCaml version to 5.1
2023-10-09 14:45:42 +01:00
Thomas Leonard
8ff3fb135f Update minimum OCaml version to 5.1
This is to allow using the new events system in future.
2023-10-09 11:04:53 +01:00
Thomas Leonard
23a1a84a13
Merge pull request #629 from talex5/linux-available
eio_linux: mark as only available on Linux
2023-10-08 16:44:42 +01:00
Thomas Leonard
5b127ad26a eio_linux: mark as only available on Linux
This avoids the CI trying to test it on e.g. FreeBSD.
2023-10-07 14:30:50 +01:00
Thomas Leonard
69c622fa01
Merge pull request #601 from SGrondin/idempotent-tests
Make MDX tests idempotent
2023-10-06 17:05:30 +01:00
Simon Grondin
6a85a11fc7 Make MDX tests idempotent 2023-10-06 16:20:55 +01:00
Thomas Leonard
4db6533fa6
Merge pull request #628 from talex5/rmtree
Add Path.rmtree ?missing_ok and allow non-directories
2023-10-06 16:20:41 +01:00
Thomas Leonard
9158f45b88 Add Path.rmtree ?missing_ok and allow non-directories
- Allow ignoring missing items.
- Allow removing non-directories (suggested by Simon Grondin).

These are both useful for tests when trying to ensure that old files
have been removed.
2023-10-06 10:27:40 +01:00
Thomas Leonard
4347a22a16
Merge pull request #627 from talex5/rmtree
Add Path.rmtree
2023-09-30 11:44:10 +01:00
Thomas Leonard
e45a9a1442 Add Path.rmtree 2023-09-30 11:33:19 +01:00
Thomas Leonard
166118bfe7
Merge pull request #625 from talex5/mkdirs
Add Path.mkdirs and Path.split
2023-09-29 14:37:10 +01:00
Patrick Ferris
fa5bc53726 Add Path.mkdirs and Path.split
Co-authored-by: Thomas Leonard <talex5@gmail.com>
2023-09-29 14:03:54 +01:00
Thomas Leonard
082bf00ccc
Merge pull request #626 from talex5/file-ops
Add Eio.File.{seek,sync,truncate}
2023-09-29 13:35:53 +01:00
Thomas Leonard
482d247694 Add Eio.File.{seek,sync,truncate} 2023-09-28 12:47:21 +01:00
Thomas Leonard
bcee2e804d Add documentation headings to Eio.Flow 2023-09-28 12:19:46 +01:00
Thomas Leonard
1ef8ad4913
Merge pull request #624 from talex5/statx-fallback
eio_linux: add fallback for statx on older kernels
2023-09-27 12:07:41 +01:00
Thomas Leonard
7c94ccbd28 eio_linux: add fallback for statx on older kernels
If statx isn't available, open the path and use fstat instead.

See: io-uring: Make statx API stable
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1b6fe6e0dfecf8c82a64fb87148ad9333fa2f62e
2023-09-27 11:43:25 +01:00
Thomas Leonard
f169082a47
Merge pull request #623 from talex5/kind
Add Eio.Path.{kind, is_file, is_directory}
2023-09-27 11:43:08 +01:00
Thomas Leonard
e14ebca077 Add Eio.Path.{kind, is_file, is_directory} 2023-09-26 17:43:22 +01:00
Thomas Leonard
9256754fe8
Merge pull request #621 from talex5/stat-docs
Document File.Stat record fields
2023-09-26 15:56:25 +01:00
Thomas Leonard
9e445f19a1 Document File.Stat record fields 2023-09-26 11:29:47 +01:00
Thomas Leonard
afb83bf1fa
Merge pull request #620 from talex5/stat
Add Path.stat
2023-09-26 09:36:25 +01:00
Patrick Ferris
1938ce5ad0 Add Path.stat
Co-authored-by: Thomas Leonard <talex5@gmail.com>
Co-authored-by: Anil Madhavapeddy <anil@recoil.org>
2023-09-25 16:27:14 +01:00
Thomas Leonard
f1be218b26
Merge pull request #618 from talex5/fstatat
Add Eio_posix.Low_level.fstatat
2023-09-23 18:07:54 +01:00
Anil Madhavapeddy
d8da454b2c Add Eio_posix.Low_level.fstatat 2023-09-22 19:33:10 +01:00
Thomas Leonard
7c4a3adc6e
Merge pull request #617 from talex5/uring-stat
Add Eio_linux.Low_level.statx
2023-09-22 19:25:35 +01:00
Patrick Ferris
602e1d8ae3 Add Eio_linux.Low_level.statx
Co-authored-by: Thomas Leonard <talex5@gmail.com>
2023-09-22 19:07:05 +01:00
Thomas Leonard
b741881d74
Merge pull request #615 from talex5/lintcstubs
Generate prototypes for C stubs from ml files
2023-09-22 11:59:02 +01:00
Thomas Leonard
3fde20c16c
Merge pull request #616 from talex5/bench-stat
Add File.stat benchmark
2023-09-20 19:34:15 +01:00
Thomas Leonard
10ed8df8fd Add File.stat benchmark 2023-09-20 17:16:15 +01:00
Thomas Leonard
137399aa6d Generate prototypes for C stubs from ml files 2023-09-19 16:43:31 +01:00
Thomas Leonard
19c42eb7a4
Merge pull request #614 from talex5/readme-env
Update README section about env
2023-08-31 11:28:41 +01:00
Thomas Leonard
d667d4fa95 Update README section about env
Spotted by Jon Sterling.
2023-08-31 09:08:23 +01:00
Thomas Leonard
ad7149dc29
Merge pull request #612 from talex5/release
Prepare release
2023-08-29 11:36:01 +01:00
Thomas Leonard
9d5aadc3eb Prepare release 2023-08-29 11:30:14 +01:00
Thomas Leonard
610b4c0653
Merge pull request #611 from talex5/simple-copy
Add Eio.Flow.Pi.simple_copy
2023-08-29 10:40:23 +01:00
Thomas Leonard
51f33d4539 Add Eio.Flow.Pi.simple_copy
This provides an easy way to implement the copy API when there are no
optimisations you want to try.
2023-08-29 10:01:53 +01:00
Thomas Leonard
387fb6d2b9
Merge pull request #610 from talex5/domains-variants
Use variant types for domain manager
2023-08-28 17:43:56 +01:00
Thomas Leonard
c8abd7ca78 Use variant types for domain manager
Also, move the mock domain manager to eio.mock so it can be used more
widely, and add `Eio.Debug.with_trace_prefix` (which it needs).
2023-08-28 08:57:43 +01:00
Thomas Leonard
286a1b743d
Merge pull request #605 from patricoferris/proc-fcm
Convert Eio.Process to FCMs
2023-08-27 09:34:48 +01:00
Patrick Ferris
b184cb4b3b Convert Eio.Process to FCMs 2023-08-27 09:22:36 +01:00
Thomas Leonard
1bba7b156a
Merge pull request #602 from talex5/pool
Add Eio.Pool
2023-08-23 09:17:21 +01:00
Thomas Leonard
f340d92a79 Add Eio.Pool
This is similar to `Lwt_pool`.
2023-08-22 16:33:17 +01:00
Thomas Leonard
5b3578a428
Merge pull request #609 from talex5/lazy
Add Eio.Lazy
2023-08-22 16:21:30 +01:00
Thomas Leonard
ec6e82b1f1 Add Eio.Lazy
If one fiber tries to force a lazy value while another is already doing
it, this will wait for the first one to finish rather than raising an
exception (as `Stdlib.Lazy` does).
2023-08-21 16:52:07 +01:00
Thomas Leonard
d8f4884e99
Merge pull request #608 from talex5/clock-variants
Convert clocks to new resource system
2023-08-20 10:28:38 +01:00
Thomas Leonard
6083d1d867 Convert clocks to new resource system
This is a continuation of #553.
2023-08-17 14:34:00 +01:00
Thomas Leonard
490873e79a
Merge pull request #606 from talex5/kcas
The README depends on kcas
2023-08-17 13:27:32 +01:00
Thomas Leonard
325bcbd494 The README depends on kcas 2023-08-17 13:06:53 +01:00
Thomas Leonard
f37f37d748
Merge pull request #603 from talex5/native-path
Add Eio.Path.native
2023-08-17 12:52:36 +01:00
Thomas Leonard
31629a17c8 Add Eio.Path.native
This is useful when interacting with non-Eio libraries, for spawning
sub-processes, and for displaying paths to users.
2023-08-17 12:08:02 +01:00
Thomas Leonard
e948fa7877
Merge pull request #592 from dra27/clarify-config
Clarify configuration for lib_eio_linux and enable tests on other arches
2023-08-16 16:38:01 +01:00
David Allsopp
d5ebfd496a Enable lib_eio_linux tests on all arches 2023-08-16 16:25:53 +01:00
David Allsopp
0d2c8f5907 Clarify the %{system} values for Linux 2023-08-16 16:25:53 +01:00
Thomas Leonard
432e1031b3
Merge pull request #604 from talex5/skip-fixbuf-test
eio_linux tests: skip fixed buffer test if not available
2023-08-16 16:25:46 +01:00
Thomas Leonard
ad0c9122a7 eio_linux tests: skip fixed buffer test if not available
This test frequently causes CI failures because the build machines are
often low on memory and refuse to provide a fixed buffer. Just skip the
test in that case.
2023-08-16 16:07:27 +01:00
Thomas Leonard
1b2ea52620
Merge pull request #600 from talex5/fix-epipe
eio_posix: ignore some errors writing to the wake up pipe
2023-08-14 09:54:45 +01:00
Thomas Leonard
561a92c33b eio_posix: ignore some errors writing to the wake up pipe
The README test failed in CI on macos with:

    # File "README.md", line 1, characters 0-0:
    # /usr/local/bin/git --no-pager diff --no-index --color=always -u _build/default/README.md _build/default/.mdx/README.md.corrected
    # diff --git a/_build/default/README.md b/_build/default/.mdx/README.md.corrected
    # index e710380..e1d7eb2 100644
    # --- a/_build/default/README.md
    # +++ b/_build/default/.mdx/README.md.corrected
    # @@ -1673,7 +1673,7 @@ Joining with the other domain
    #
    #  ```ocaml
    #  # y + Domain.join foreign_domain
    # -- : int = 42
    # +Exception: Unix.Unix_error(Unix.EPIPE, "single_write", "")
    #  ```
    #
    #  we arrive at the answer.

Ignore EPIPE when writing, since it means we've shut down and don't
need a wake-up (the event has presumably already been processed).

Also, ignore EAGAIN and EWOULDBLOCK. If the pipe is full, then a wake-up
is already pending.
2023-08-12 09:09:13 +01:00
Thomas Leonard
1493f01779
Merge pull request #598 from talex5/short-writes
Add Flow.single_write
2023-08-12 08:49:57 +01:00
Thomas Leonard
24571692f1
Merge pull request #597 from talex5/windows-fixes
Fix some MDX problems on Windows
2023-08-11 11:38:20 +01:00
Thomas Leonard
38a9d93620 Add Flow.single_write
`Flow.write` keeps writing until all the data is sent or an error
occurs, but sometimes it's useful to perform only a single write
operation. For example, if `Buf_write` asks to write 5 bytes and only 4
get written, we would previously do another 1-byte write. However, there
may have been more data available by then.

This is also useful for error recovery, if you need to know exactly how
many bytes were successfully written before the error.

Note that the `Buf_write` tests for `Read_source_buffer` were supposed
to test that the target flow read the buffers directly, but since we
started using `write` instead of `copy` it doesn't matter.
2023-08-11 11:17:33 +01:00
Vesa Karvonen
f81880d293 Fix some MDX problems on Windows
- The `c_library_flags` are required to allow the stubs to be loaded dynamically by MDX.
- Fixed `dune` file to use `-cclib` as the `-l` options are for the linker rather than the compiler.
2023-08-11 09:57:24 +01:00
Thomas Leonard
b755d9e41f
Merge pull request #522 from talex5/fd-passing
Eio_unix: add FD passing
2023-08-11 09:53:35 +01:00
Thomas Leonard
90af8f755a Remove Unix_fd from unix network socket types
The normal ways of getting a socket don't include it anyway, and we can
infer it from something being a Unix socket.
2023-08-11 09:42:25 +01:00
Thomas Leonard
57d08881dc Add FD passing 2023-08-10 18:16:39 +01:00
Thomas Leonard
bb474070bb
Merge pull request #553 from talex5/variants
Replace objects with variants
2023-08-10 14:58:45 +01:00
Thomas Leonard
95c91c061c Use variant types in many places
Jane Street have requested that Eio not use objects. This commit
switches to an alternative scheme for representing OS resources using
variants instead. The changes for users of the library are minimal -
only the types change. The exception to this is if you want to provide
your own implementations of resources, in which case you now provide a
module rather than a class. The (small) changes to the README give a
good idea of the user-facing effect.
2023-08-10 09:50:58 +01:00
Thomas Leonard
47f4d2034c
Merge pull request #593 from talex5/fork-alloc
Fork actions must not allocate
2023-07-28 10:15:43 +01:00
Thomas Leonard
5d8a48c20d Fork actions must not allocate
The `execve` action allocated the arrays in the forked child process.
However, in a multi-threaded program we might have forked while another
thread had the malloc lock. In that case, the child would wait forever
because it inherited the locked mutex but not the thread that would
unlock it. e.g.

    #0  futex_wait (private=0, expected=2, futex_word=0xffff9509cb10 <main_arena>) at ../sysdeps/nptl/futex-internal.h:146
    #1  __GI___lll_lock_wait_private (futex=futex@entry=0xffff9509cb10 <main_arena>) at ./nptl/lowlevellock.c:34
    #2  0x0000ffff94f8e780 in __libc_calloc (n=<optimized out>, elem_size=<optimized out>) at ./malloc/malloc.c:3650
    #3  0x0000aaaac67cfa68 in make_string_array (errors=errors@entry=37, v_array=281472912006504) at fork_action.c:47
    #4  0x0000aaaac67cfaf4 in action_execve (errors=37, v_config=281472912003024) at fork_action.c:61
    #5  0x0000aaaac67cf93c in eio_unix_run_fork_actions (errors=errors@entry=37, v_actions=281472912002960) at fork_action.c:19
2023-07-28 09:56:03 +01:00
Thomas Leonard
95a58dc711 Fix compiler warning 2023-07-28 09:56:03 +01:00
Thomas Leonard
355f8dabf2
Merge pull request #591 from avsm/sync-opam-files
sync opam files with dune generation
2023-07-22 17:54:28 +01:00
Anil Madhavapeddy
5fabef0fd7 sync opam files with dune generation
This is needed after #588
2023-07-22 16:42:29 +01:00
Thomas Leonard
e5a0e54bc3
Merge pull request #588 from talex5/win32
eio_windows: update available line to win32
2023-07-20 13:50:29 +01:00
Thomas Leonard
5c5aa23df3 eio_windows: update available line to win32
This is to match the change in #557.
2023-07-20 10:46:07 +01:00
Thomas Leonard
e10e176da3
Merge pull request #586 from SGrondin/process-exit-success
Add `Process.run ?is_success` to control definition of success
2023-07-20 10:38:57 +01:00
Simon Grondin
7bb7c2b59b Add Process is_success 2023-07-20 09:34:39 +01:00
Thomas Leonard
3fe575538d
Merge pull request #583 from talex5/release
Prepare release
2023-07-12 15:40:56 +01:00
Thomas Leonard
d29cf2e4ca Prepare release 2023-07-12 13:35:51 +01:00
Thomas Leonard
b3b3c9fd3e
Merge pull request #557 from dra27/cygwin-opam-file
Correct the backend selection for Cygwin
2023-07-12 13:32:44 +01:00
David Allsopp
406efc8ff9 Restrict Windows, not Windows+Cygwin 2023-07-12 12:23:44 +01:00
Thomas Leonard
f69ba326ec
Merge pull request #582 from talex5/build-if
Simplify dune files with dune 3.9's build_if
2023-07-12 12:17:09 +01:00
Thomas Leonard
733f71f3a4 Simplify dune files with dune 3.9's build_if
This removes the work-around added in 3ae2925cfe373d0f01f06d3cbe84df3fe29693d8.
2023-07-12 11:13:46 +01:00
Thomas Leonard
7251db6a1b
Merge pull request #581 from talex5/pread-eof
eio_posix: fix pread at end-of-file
2023-07-12 10:50:08 +01:00
Thomas Leonard
38d97996dc eio_posix: fix pread at end-of-file
Reported by Simon Grondin.
2023-07-11 10:56:34 +01:00
Thomas Leonard
57ace76003
Merge pull request #574 from talex5/fix-invalid-fd
Eio_posix: fix update to watched FDs on cancel
2023-07-08 11:17:11 +01:00
Thomas Leonard
fc68c00ac6 Workaround CI solver limitation
This temporarily reverts #542 until the CI supports it.
2023-07-08 09:18:35 +01:00
Thomas Leonard
cc19aa1606 Eio_posix: fix update to watched FDs on cancel
Reported by Daniel Quernheim.
2023-07-07 17:48:37 +01:00
Thomas Leonard
b4b3540694
Merge pull request #571 from talex5/domain-backtraces
Preserve backtraces across Domain_manager.run
2023-07-07 17:24:26 +01:00
Anil Madhavapeddy
bd4cf9fec1
Merge pull request #542 from talex5/available-windows
Mark eio_windows as only available on Windows
2023-07-05 10:06:46 +01:00
Thomas Leonard
091316a0e6 Preserve backtraces across Domain_manager.run 2023-07-03 09:47:10 +01:00
Thomas Leonard
1c891a45cf
Merge pull request #569 from talex5/fix-empty-path
Fix handling of empty path strings
2023-07-03 09:47:02 +01:00
Thomas Leonard
c87893cf05 Fix handling of empty path strings
`readdir (dir, ".")` worked, but `readdir (dir, "")` didn't.

Also, improve formatting of empty paths (`<cwd:>` becomes `<cwd>`).

Fixes #559 (reported by Simon Grondin).
2023-06-29 10:42:56 +01:00
Thomas Leonard
75c27bf50e
Merge pull request #567 from talex5/single-waiter
Move Waiters out of Eio_core
2023-06-27 09:15:25 +01:00
Thomas Leonard
d063513ddf Move Waiters and Hook out of Eio_core
Nothing there needs them now.
2023-06-26 16:42:40 +01:00
Thomas Leonard
35b93c0c2e Use Single_waiter in Switch
This is more efficient, and is also a step towards removing Waiters
completely.
2023-06-26 16:42:40 +01:00
Thomas Leonard
dcf8624cde
Merge pull request #568 from talex5/thread-safety
Add some notes about thread-safety in the documentation
2023-06-26 16:40:48 +01:00
Thomas Leonard
767f90d964 Add some notes about thread-safety in the documentation 2023-06-26 14:49:12 +01:00
Thomas Leonard
3e49036b63
Merge pull request #562 from talex5/waitpid-interop
Eio_posix: don't reap non-Eio child processes
2023-06-23 19:23:43 +01:00
Thomas Leonard
43308adfe2 Eio_posix: don't reap non-Eio child processes
Previously, eio_posix installed a signal handler that would reap all
child processs. However, this may cause problems if some other library
is used to spawn a process (e.g. `Stdlib.Unix` or `Lwt`).

Now, it polls every known child process individually when signalled,
so only processes spawned by Eio will be reaped.

Eio_linux is unaffected, as it uses process descriptors instead.
2023-06-23 19:09:56 +01:00
Thomas Leonard
da958b6d81
Merge pull request #563 from talex5/condition-extras
Extend Condition API
2023-06-23 16:22:38 +01:00
Thomas Leonard
6d286e82fb Add Eio.Condition.loop_no_mutex
This is simpler and more efficient than using `Fiber.first` to await the
condition in one fiber while doing the work in another.
2023-06-23 16:06:58 +01:00
Thomas Leonard
8c79ca833c Add Eio.Condition.register_immediate
This is to allow integration with other IO libraries, such as Lwt.
2023-06-22 15:59:22 +01:00
Thomas Leonard
6d782081d2
Merge pull request #560 from bord-o/expose_eio_backend
Expose eio backend in posix, windows, and linux backends
2023-06-20 10:59:11 +01:00
bordo
148f8cd3cb Add Eio.Stdenv.backend_id for debugging 2023-06-20 10:57:16 +01:00
Thomas Leonard
a6c0be9aff
Merge pull request #552 from talex5/deprecate
Remove deprecated features
2023-06-08 12:36:10 +01:00
Thomas Leonard
feeeac9e55 Remove deprecated features 2023-06-08 11:31:22 +01:00
Thomas Leonard
d262afbb37
Merge pull request #549 from talex5/socket-close
Restore close to socket type
2023-06-08 11:11:12 +01:00
Thomas Leonard
cc400c27b1 Restore close to socket type
Now that double-close is permitted, we can simplify the types by making
all sockets closable.

This reverts commit ccf147474ee3c2c222c2e9c7694b2c69445734c8.
2023-06-06 11:35:22 +01:00
Thomas Leonard
264a7d0751
Merge pull request #547 from talex5/double-close-ok
Allow calling close more than once
2023-06-06 11:08:47 +01:00
Thomas Leonard
99ee95172e Allow calling close more than once
Previously, some resources raised an exception if you tried to close
them more than once, while others ignored it. Document that calling
close on a closed resource does nothing.

This is useful for `accept_fork`, which always tries to close the socket
at the end, but would like to allow the user to close it sooner if they
wish.

Requested by Antonio Nuno Monteiro.

I also moved the definition of `close` to `Generic`, since it's not
specific to flows.
2023-06-06 10:14:30 +01:00
Thomas Leonard
dbcbd64882
Merge pull request #543 from talex5/pp-signal
Use `Fmt.Dump.signal` to format signals
2023-06-04 10:48:51 +01:00
Thomas Leonard
ce83e85327 Use Fmt.Dump.signal to format signals
Also, make the message clearer and avoid "signaled" vs "signalled"
confusion.
2023-06-02 15:11:47 +01:00
Thomas Leonard
5f171313ff Mark eio_windows as only available on Windows 2023-06-02 13:51:22 +01:00
Thomas Leonard
85adab31bc
Merge pull request #541 from talex5/release
Update documentation for Eio 0.10
2023-06-02 13:50:23 +01:00
Thomas Leonard
081e7549ba Update documentation for Eio 0.10 2023-06-02 12:08:43 +01:00
Thomas Leonard
687ebf99a6
Merge pull request #540 from talex5/release
Prepare release
2023-06-02 11:55:58 +01:00
Thomas Leonard
1437b238a6 Prepare release 2023-06-02 11:23:52 +01:00
Thomas Leonard
73ce323948
Merge pull request #509 from patricoferris/fs-windows
Fs implementation for windows
2023-06-02 11:20:05 +01:00
Patrick Ferris
9cf5f35063 eio_windows: initial filesystem implementation 2023-06-02 09:41:00 +01:00
Thomas Leonard
bd77643795
Merge pull request #532 from patricoferris/windows-no-pins
Remove pins for windows
2023-06-02 09:40:46 +01:00
Patrick Ferris
75ce40e26b Remove pins for windows 2023-06-02 09:25:57 +01:00
Thomas Leonard
0325b6742c
Merge pull request #539 from talex5/fix-flush
Buf_write: fix flush returning too early
2023-06-02 09:25:49 +01:00
Thomas Leonard
d47d5779b3 Buf_write: fix flush returning too early
`flush` previously only waited until the data had been taken out of the
buffer (i.e. received by the underlying flow), but did not wait for it
to be transmitted.

See https://github.com/ocaml-multicore/eio/issues/527.
2023-06-02 09:16:34 +01:00
Thomas Leonard
ac99f3162c
Merge pull request #533 from avsm/ignore-notconn
ignore ENOTCONN errors on socket shutdown
2023-06-02 09:15:05 +01:00
Anil Madhavapeddy
0453510d5a Ignore ENOTCONN errors on socket shutdown
macOS can return ENOTCONN occasionally even if the other side did
a graceful shutdown.  This changes all the backends to ignore
ENOTCONN; even though it's unlikely to show up on Linux, there's
still not much point throwing the exception from a shutdown on any
of the platforms.

Fixes #521
2023-06-01 09:55:52 +01:00
Thomas Leonard
c0b2cd0c4c
Merge pull request #537 from talex5/fix-socket-close
Remove close from socket type
2023-06-01 08:55:43 +01:00
Thomas Leonard
ccf147474e Remove close from socket type
Sockets created by `accept_fork` can't be closed manually currently, so
revert the part of #516 that said they could.

We might want to revisit this later, but for now put things back how
they were.

Reported by Anil Madhavapeddy.
2023-06-01 06:51:04 +01:00
Thomas Leonard
739cb87f97
Merge pull request #538 from talex5/changelog
Update changelog
2023-05-31 15:45:20 +01:00
Thomas Leonard
349a41c716 Update changelog 2023-05-31 14:35:14 +01:00
Thomas Leonard
b879f87b84
Merge pull request #500 from ocaml-multicore/current-bench
Run benchmarks with current-bench
2023-05-31 13:21:24 +01:00
Sudha Parimala
8fa37d51f5 More benchmarks to current-bench
Co-authored-by: Thomas Leonard <talex5@gmail.com>
2023-05-31 10:54:51 +01:00
Anil Madhavapeddy
bf25e24f9d
Merge pull request #530 from patricoferris/windows-vendor-stubs
Windows vendor stubs
2023-05-28 17:27:19 +01:00
Patrick Ferris
81325ebae7 Tidy GHA for windows 2023-05-27 23:40:26 +01:00
Patrick Ferris
9793ae03f7 Vendor cstruct stubs and remove pins 2023-05-27 23:34:06 +01:00
Patrick Ferris
bce6a871b8 Pin ocamlbuild 2023-05-27 23:33:51 +01:00
Patrick Ferris
92c16d9405 Add dscheck to windows test CI deps 2023-05-27 15:45:56 +01:00
Patrick Ferris
bc8bd22f39 Allow windows users to run dune build 2023-05-27 15:01:49 +01:00
Thomas Leonard
0b18c3c060
Merge pull request #524 from talex5/linux-fork
eio_linux: fallback to fork if clone3 is unavailable
2023-05-26 16:46:54 +01:00
Thomas Leonard
3d96450533 eio_linux: fallback to fork if clone3 is unavailable
`clone3` is blocked by Docker's default security policy.

Also, use `(uintptr_t)` when storing a pointer in a `uint64_t` field,
otherwise it doesn't work on 32-bit systems.
2023-05-25 12:04:08 +01:00
Thomas Leonard
1cd01b5218
Merge pull request #526 from talex5/fix-5.1-tests
Fix MDX tests on OCaml 5.1
2023-05-25 11:51:19 +01:00
Thomas Leonard
188dc9ae6d Fix MDX tests on OCaml 5.1
Using `Fmt.unit` causes blank lines to appear in the output!
See: https://github.com/realworldocaml/mdx/issues/429
2023-05-25 11:16:31 +01:00
Thomas Leonard
addef140c3
Merge pull request #523 from talex5/windows-stdenv
eio_windows: use Eio_unix.Stdenv.base type
2023-05-24 12:30:42 +01:00
Thomas Leonard
f959c72504 eio_windows: use Eio_unix.Stdenv.base type
Otherwise, it can't be used with `Eio_main.run`.

Also, get CI to test an example using `Eio_main`.
2023-05-24 11:49:44 +01:00
Thomas Leonard
a407535606
Merge pull request #516 from talex5/unix-net
Add Eio_unix.Net module
2023-05-23 10:44:27 +01:00
Thomas Leonard
41c3a2baa4 Add Eio_unix.Net and fix stream/datagram confusion
- Add `Eio_unix.Net` module and move network-related items from
  `Eio_unix` to there.
- Replace `Eio_unix.socketpair` with
  `Eio_unix.Net.socketpair_{stream,datagram}`. Previously, it took a
  type argument (stream or datagram), but always returned a `stream_socket`.
- Add `Eio_unix.Net.import_socket_datagram`.
- Move sockaddr conversions to `eio.unix` so they can be shared.
- Implement `getnameinfo` in `eio.unix` to share it by default.
- `Eio_linux.Low_level.send_msg` no longer throws away the number of
  bytes sent.
- Add `close` to all socket types.
- The destination in `send` is now optional.
- `send` now takes a iovec, not just a single buffer.
  The stream API allows this and we tested it for datagrams using
  that API due to the previous type confusion.
- Allow `Unix` socket paths as datagram addresses too.
- Add `send_msg` and `recv_msg` stubs to Eio_posix.
  This avoids converting to bytes, and will be useful to add FD passing
  later.
2023-05-22 20:14:25 +01:00
Thomas Leonard
21c6a54200
Merge pull request #520 from talex5/cancel-not-error
Don't call `accept_fork`'s error handler on cancellation
2023-05-18 14:25:39 +01:00
Thomas Leonard
7c984b7f0a Don't call accept_fork's error handler on cancellation
This typically happens when the server is shutting down and wants to
stop all connection handlers. There is no need to have each connection
fiber log this; they just need to stop.
2023-05-18 10:11:02 +01:00
Thomas Leonard
c1e3acabe1
Merge pull request #519 from talex5/stress-proc
Add stress test for spawning processes
2023-05-18 10:09:21 +01:00
Thomas Leonard
d3068fedc9 Add stress test for spawning processes 2023-05-17 11:42:52 +01:00
Thomas Leonard
7e9c4c038b
Merge pull request #518 from talex5/fix-pp-status
Eio.Process.pp_status should be polymorphic
2023-05-16 15:07:08 +01:00
Thomas Leonard
55b71cb28a Eio.Process.pp_status should be polymorphic
Otherwise, you can't print an exit status.
2023-05-16 13:20:39 +01:00
Sudha Parimala
42ea36b8ea
Merge pull request #515 from talex5/link-meeting
Link to developer meetings information
2023-05-12 16:50:34 +05:30
Thomas Leonard
1cf3f2c733 Link to developer meetings document 2023-05-12 11:28:31 +01:00
Thomas Leonard
950b1a881e
Merge pull request #511 from talex5/fix-build
Don't run Windows discover script on other platforms
2023-05-08 19:04:01 +01:00
Thomas Leonard
702299d956 Don't run Windows discover script on other platforms
Otherwise, `dune build` will try to run it.
2023-05-08 17:58:03 +01:00
Thomas Leonard
439e53862a
Merge pull request #497 from patricoferris/posix-windows
Posix-based windows implementation
2023-05-08 17:52:53 +01:00
Patrick Ferris
cab124d5e0 Don't allocate OCaml exceptions in Windows fork actions 2023-05-08 16:09:37 +01:00
Thomas Leonard
b5b5de777f
Merge pull request #508 from talex5/cleanup
Deprecation cleanups
2023-05-05 14:31:40 +01:00
Thomas Leonard
bc16038285 Fix Process link in README
Link to the mli, not the ml file.
2023-05-05 14:05:49 +01:00
Thomas Leonard
357a8f7169 Deprecate Fiber.fork_sub
Originally, you needed the parent switch to create the child one, but
that happens automatically now, so we don't need a special function for
this.
2023-05-05 14:05:49 +01:00
Patrick Ferris
393ec686e3
Add kcas 2023-05-05 13:04:11 +01:00
Patrick Ferris
9a9c02ba08 Support DLA on Windows 2023-05-05 12:44:42 +01:00
Patrick Ferris
e5b57fab32 Fix rebase mistake in stubs 2023-05-05 00:29:41 +01:00
Patrick Ferris
f3d70518fa Add more windows tests and fix network code 2023-05-05 00:26:20 +01:00
Patrick Ferris
695499e214 Disable MDX tests on Windows 2023-05-05 00:26:19 +01:00
Patrick Ferris
fa934d09c2 Use ifndef
Co-authored-by: Thomas Leonard <talex5@gmail.com>
2023-05-05 00:26:19 +01:00
Patrick Ferris
106ea8a797 Fix bug introduced in fork_actions 2023-05-05 00:26:19 +01:00
Patrick Ferris
46105436d2 Add tuareg condition to windows test 2023-05-05 00:26:19 +01:00
Patrick Ferris
7fe1080e94 Remove Fs from windows 2023-05-05 00:26:19 +01:00
Patrick Ferris
0917e26e3e Update comments and stubs 2023-05-05 00:26:19 +01:00
Patrick Ferris
40d758b362 Remove incorrect Obj.magic 2023-05-05 00:26:19 +01:00
Patrick Ferris
ec54ccb9e7 More posix to windows clean up 2023-05-05 00:26:18 +01:00
Patrick Ferris
4bd46b5e84 Update CI 2023-05-05 00:26:18 +01:00
Patrick Ferris
a9e2943a92 Update comments 2023-05-05 00:26:18 +01:00
Patrick Ferris
e6092958a7 Remove deprecated FD module 2023-05-05 00:26:18 +01:00
Patrick Ferris
cf6ba07b45 Remove unused process module 2023-05-05 00:26:18 +01:00
Patrick Ferris
29d41d4023 Posix-based windows implementation 2023-05-05 00:26:13 +01:00
Thomas Leonard
df197a82ce Remove some deprecated APIs
These were all already marked as deprecated in Eio 0.8.
2023-05-04 20:56:23 +01:00
Thomas Leonard
7ea93f2bc3
Merge pull request #499 from talex5/proc-redux
Cross-platform subprocess support
2023-05-04 17:51:00 +01:00
Thomas Leonard
0c28312081
Merge pull request #507 from talex5/optional-flags
eio_posix: probe for existence of some flags
2023-05-04 17:35:20 +01:00
Patrick Ferris
86501ab97f Initial cross-platform subprocess support
This also splits Eio_unix into separate modules.

Co-authored-by: Thomas Leonard <talex5@gmail.com>
2023-05-04 16:46:56 +01:00
Thomas Leonard
21b7f12322 eio_posix: probe for existence of some flags
FreeBSD 12 didn't have `O_DSYNC`. Also, add `O_RESOLVE_BENEATH` and
`O_PATH` if available.
2023-05-04 15:59:59 +01:00
Thomas Leonard
a92f329ff2
Merge pull request #505 from patricoferris/fix-is-blocking
Fix eio_unix_is_blocking
2023-05-04 09:37:49 +01:00
Patrick Ferris
fdc396126f Fix eio_unix_is_blocking 2023-05-03 22:42:31 +01:00
Thomas Leonard
5ba047f25d
Merge pull request #503 from talex5/fix-kcas
Fix test dependency on kcas
2023-05-03 15:36:10 +01:00
Thomas Leonard
170f7e4244 Fix test dependency on kcas
The README file is tested by `eio_main`, not `eio`.
2023-05-03 15:24:20 +01:00
Thomas Leonard
543d77be5a
Merge pull request #494 from ocaml-multicore/add-domain-local-await-support
Add support for domain local await
2023-05-03 10:43:41 +01:00
Vesa Karvonen
f55c105f8a Add support for domain local await
Co-authored-by: Thomas Leonard <talex5@gmail.com>
2023-05-03 10:29:05 +01:00
Thomas Leonard
180c44dab1
Merge pull request #501 from ocaml-multicore/code-of-conduct
Adopt OCaml Code of Conduct
2023-05-03 09:29:50 +01:00
Sudha Parimala
e8bb91c1a6 Adopt OCaml Code of Conduct
This code of conduct lives in [ocaml/code-of-conduct](https://github.com/ocaml/code-of-conduct) and has been discussed in [this thread](https://discuss.ocaml.org/t/ocaml-community-code-of-conduct/10494).

It has been adopted by the OCaml compiler in [ocaml/ocaml#11761](https://github.com/ocaml/ocaml/pull/11761) and many platform tools. After a discussion with maintainers, we propose adopting it for Eio.
2023-05-03 13:04:49 +05:30
Thomas Leonard
24e6238e6d
Merge pull request #498 from talex5/stdenv
Move Eio.Stdenv.t to Eio_unix.Stdenv.base
2023-05-02 11:26:01 +01:00
Thomas Leonard
2f0425edee Move Eio.Stdenv.t to Eio_unix.Stdenv.base
The old type wasn't directly useful for anything, since nothing provides
exactly this set of resources, and nothing needs to consume exactly this
set either. However, it is useful in Eio_unix as the set of resources
that a backend needs to provide to be compatible with `Eio_main.run`.

The new type is almost the same for now, except that the standard
streams include file descriptors. Other Unix-specific features can be
added later, such as support for passing file descriptors over sockets
or to child processes.
2023-05-02 10:57:03 +01:00
Thomas Leonard
92f416f0e8
Merge pull request #496 from talex5/links
Add README links to Meio and Lambda Capabilities blog post
2023-04-27 11:47:43 +01:00
Thomas Leonard
2a1478b695 Link to Lambda Capabilities blog post 2023-04-27 11:03:49 +01:00
Thomas Leonard
45c9a50641 Link to Meio 2023-04-27 10:57:34 +01:00
Thomas Leonard
fc9960e138
Merge pull request #491 from talex5/unify-fd
Unify Eio_linux.FD and Eio_posix.Fd as Eio_unix.Fd
2023-04-21 15:35:26 +01:00
Thomas Leonard
b274000728 Add Eio_unix.Fd.pp 2023-04-21 10:19:46 +01:00
Thomas Leonard
2fdd4cc712 Eio_linux: don't use lazy in stdenv
It doesn't work with multiple domains.
2023-04-21 10:19:46 +01:00
Thomas Leonard
0fb95cdf1b Deprecate old FD APIs
The tests now use the new ones.
2023-04-21 10:19:46 +01:00
Thomas Leonard
0a8762af4b Unify Eio_linux.FD and Eio_posix.Fd as Eio_unix.Fd
Now that eio_luv is gone, there is no need for different backends to
have different types for wrapped file descriptors. This means we can use
wrapped FDs in more places, avoiding races.

This also allows e.g. `Eio_unix.pipe` to return resources with an Fd.t
attached explicitly in the type. It also avoids duplicating the
fork actions in each backend.

In this commit the old APIs are mostly still present, and the tests
still use them.
2023-04-21 10:19:46 +01:00
Thomas Leonard
dfb50bf01d
Merge pull request #493 from talex5/fix-test-race
Fix race in ctf tests
2023-04-20 20:41:43 +01:00
Thomas Leonard
9831ed81c6 Fix race in ctf tests
A missing `Domain.join` could cause the output to appear in the wrong
place.
2023-04-20 17:32:49 +01:00
Thomas Leonard
b5d56f7acd
Merge pull request #492 from RyanGibb/mirage-ipaddr-conversion-docs
Document mirage `Ipaddr` conversion
2023-04-20 17:11:56 +01:00
Ryan Gibb
14e0bde730 Document mirage Ipaddr conversion
Co-authored-by: Patrick Ferris <patrick@sirref.org>
Co-authored-by: Thomas Leonard <talex5@gmail.com>
2023-04-19 17:27:27 +01:00
Thomas Leonard
8ce7edecb6
Merge pull request #489 from talex5/domainslib
Document how to use Domainslib from Eio
2023-04-18 11:09:22 +01:00
Thomas Leonard
aa4ae98d62 Document how to use Domainslib from Eio 2023-04-17 20:05:52 +01:00
Thomas Leonard
9656b89ffa
Merge pull request #487 from talex5/fix-cond
Fix Condition.await bug when cancelling
2023-04-14 16:17:24 +01:00
Thomas Leonard
4fac85f8b0 Fix Condition.await bug when cancelling
`Condition.await` tries to re-acquire the lock when cancelled. However,
if another fiber is holding the lock then this operation must wait and
would itself get cancelled.

Reported by Vesa Karvonen <vesa.a.j.k@gmail.com>
2023-04-14 10:54:31 +01:00
Thomas Leonard
1277485f9b
Merge pull request #485 from talex5/remove-luv
Remove eio_luv backend
2023-04-13 11:02:42 +01:00
Thomas Leonard
e605f06c29 Remove eio_luv backend and add skeleton eio_windows replacement 2023-04-13 09:43:33 +01:00
Thomas Leonard
72f9d77642
Merge pull request #484 from talex5/release
Prepare release
2023-04-11 09:27:01 +01:00
Thomas Leonard
cdaa323983 Prepare release 2023-04-10 20:02:53 +01:00
Thomas Leonard
233cf01006
Merge pull request #482 from zenfey/syscall_of_getrandom_for_glibc_version_before_2_25
use raw system call of getrandom for glibc version before 2.25
2023-04-09 10:28:56 +01:00
Thomas Leonard
39179a2f4e
Merge pull request #481 from talex5/changelog
Update changelog
2023-04-06 09:19:44 +01:00
ZFY
c1f47f805e
raw system call of getrandom for glibc<2.25
getrandom were added in glibc in version 2.25.  Compiling will work to use raw system call for glibc version ahead of 2.25
2023-04-06 00:15:16 +08:00
ZFY
50558576b6
Update eio_stubs.c
getrandom and getentropy were added in glibc in version 2.25.  Compiling will work to use raw system call for glibc version ahead of 2.25
2023-04-06 00:07:40 +08:00
Thomas Leonard
244581bcce Update changelog 2023-04-05 15:59:55 +01:00
Thomas Leonard
2f9ebe1955
Merge pull request #480 from TheLortex/exclusive-ids
Mint exclusive IDs
2023-04-05 11:02:36 +01:00
Lucas Pluvinage
bcc49970cf Mint exclusive IDs across domains 2023-04-04 19:42:05 +01:00
Thomas Leonard
83b764cf96
Merge pull request #478 from talex5/bench-server
Add a network benchmark using an HTTP-like protocol
2023-04-04 19:29:23 +01:00
Thomas Leonard
3c7277f220 Add a network benchmark using an HTTP-like protocol
This runs 4 client domains and 4 server domains. Each client opens 25
connections and makes 1000 HTTP POSTs on each one. For each request it
uses Buf_write to write the headers and body, then Buf_read to parse the
response. Likewise, the server uses Buf_read and Buf_write to handle the
each request.

On my system, I get this using eio_linux (eio_posix is about the same):

    clients, servers, requests, requests/s
          4,       4,   100000, 367051.7
2023-04-04 15:00:15 +01:00
Thomas Leonard
7c625fa7c3
Merge pull request #477 from talex5/posix-yield
eio_posix: yield on IO
2023-03-29 18:00:51 +01:00
Thomas Leonard
609b13c2dd eio_posix: yield on IO
Before, a read would only yield if it needed to block. This could
prevent other fibers from running at all if data was always available
and also prevented it from checking for cancellation.

Also, for operations on paths, run the operation in a system thread.

This could be optimised a bit, but this makes it correct at least.
2023-03-29 15:13:50 +01:00
Thomas Leonard
d051d31257
Merge pull request #476 from talex5/release-uring
eio_linux: release uring if Linux is too old
2023-03-28 15:08:09 +01:00
Thomas Leonard
19701cb7f8 eio_linux: release uring if Linux is too old
If we manage to create a uring but find it doesn't support things we
need we report an error. But we should release it too.
2023-03-28 12:46:25 +01:00
Thomas Leonard
dd66ff981e
Merge pull request #472 from talex5/linux-process
eio_linux: add subprocess support
2023-03-28 12:43:13 +01:00
Thomas Leonard
10486fe2eb Compatibility for Debian 10 2023-03-28 11:08:24 +01:00
Thomas Leonard
38cc9d5b46 eio_linux: add subprocess support
Co-authored-by: Patrick Ferris <patrick@sirref.org>
2023-03-28 11:08:24 +01:00
Thomas Leonard
6d95b9e37d
Merge pull request #475 from talex5/fix-dune-build-on-non-linux-systems
Work around various opam and dune issues on non-linux systems
2023-03-28 11:06:22 +01:00
Vesa Karvonen
fb9225778a Only dune exec -- ./lib_eio_linux/tests/bench_noop.exe on linux
Otherwise `make bench` fails:

    dune exec -- ./lib_eio_linux/tests/bench_noop.exe
    Error: Program "./lib_eio_linux/tests/bench_noop.exe" not found!
    make: *** [bench] Error 1
2023-03-27 17:05:27 +01:00
Vesa Karvonen
02f6954348 Allow eio_linux package to be empty using (allow_empty)
The use of undocumented `(allow_empty)` was suggested by dune:

    ➜  eio git:(main) ✗ dune build
    Error: The package eio_linux does not have any user defined stanzas attached
    to it. If this is intentional, add (allow_empty) to the package definition in
    the dune-project file
    -> required by _build/default/eio_linux.install
    -> required by alias all
    -> required by alias default
2023-03-27 17:05:26 +01:00
Vesa Karvonen
3ae2925cfe Work around (enabled_if ...) not working properly in dune
➜  eio git:(main) ✗ dune build
    File "lib_eio_linux/tests/dune", line 30, characters 21-30:
    30 |  (libraries alcotest eio_linux))
                              ^^^^^^^^^
    Error: Library "eio_linux" in _build/default/lib_eio_linux is hidden
    (unsatisfied 'enabled_if').
    -> required by _build/default/lib_eio_linux/tests/test.exe
    -> required by alias lib_eio_linux/tests/all
    -> required by alias default
2023-03-27 17:04:45 +01:00
Thomas Leonard
bee7d1675a
Merge pull request #474 from talex5/linux-pipe-errors
eio_linux: improve error handling creating pipes and sockets
2023-03-27 12:44:36 +01:00
Thomas Leonard
4c6f020e9c eio_linux: improve error handling creating pipes and sockets
If we get an error (e.g. too many FDs) then report it to the calling
fiber, instead of exiting the event loop.

This matches the behaviour in eio_posix.
2023-03-27 11:05:42 +01:00
Thomas Leonard
8d005fff87
Merge pull request #471 from talex5/update-docker
Update Dockerfile
2023-03-24 11:59:04 +00:00
Thomas Leonard
a4305b200a Update Dockerfile
This also adds a CI action to check it.
2023-03-23 17:09:01 +00:00
Thomas Leonard
0a96e92880
Merge pull request #470 from talex5/linux-exit
eio_linux: wait for uring to finish before exiting
2023-03-23 12:49:08 +00:00
Thomas Leonard
2a0c5acf06
Merge pull request #464 from talex5/inherit-fds
Add inherit_fds fork action
2023-03-23 11:14:56 +00:00
Thomas Leonard
3b309e8066 Add inherit_fds fork action 2023-03-23 10:54:59 +00:00
Thomas Leonard
f7fa3ad69b eio_linux: wait for uring to finish before exiting
If a fiber raises an exception then the mainloop exits immediately.
This normally indicates a bug in Eio, since spawned fibers have
exception handlers that pass the error to the switch instead. However,
that wasn't the case for the root fiber.

If the root fiber raised an exception after submitting a cancel
operation then we would try to exit the ring too early, resulting in:

    Invalid_argument("exit: 1 request(s) still active!")

Fixes #467.
2023-03-23 10:00:43 +00:00
Thomas Leonard
2c365aa911
Merge pull request #468 from avsm/fix-cond-ocamldoc
Condition: fix the example in the docstring
2023-03-22 10:22:51 +00:00
Anil Madhavapeddy
2c4a01b29e Condition: fix the example in the docstring
The interface changed in 49c22696d50ed20b9ff68c1b1bc6804f85ab99d6
to make Mutex.t a required argument to Condition.await
2023-03-21 21:12:26 +00:00
Thomas Leonard
5ffe8019f5
Merge pull request #466 from talex5/split
eio_linux: split into multiple files
2023-03-21 16:27:29 +00:00
Thomas Leonard
6b557ed40b eio_linux: split into multiple files 2023-03-21 15:00:01 +00:00
Thomas Leonard
b70b4b6064
Merge pull request #465 from talex5/tidy-linux
Prepare to split eio_linux into separate files
2023-03-21 14:34:13 +00:00
Thomas Leonard
37ec03459b eio_linux: split out core effects
Separate out the events that need to be handled in the core scheduler
from the extras, which need to depend on the Low_level module.
2023-03-21 11:37:07 +00:00
Thomas Leonard
1353ee067e eio_linux: reduce the number of effects used 2023-03-21 11:37:07 +00:00
Thomas Leonard
0f0d4072cc eio_linux: refactor code to allow splitting file
The goal here is to allow the code to be split into multiple files more
easily, and to make it more similar to the structure of eio_posix.

- Store the configuration parameters in a record.
- Rename `run` as `run_event_loop` and move stdenv out of it.
- Use `Multiple` exception (to minimise use of logging).
2023-03-21 11:36:15 +00:00
Thomas Leonard
de3153c6bf
Merge pull request #463 from talex5/fix-doc
Update doc comment example to new function name
2023-03-20 19:42:48 +00:00
Thomas Leonard
a48368c3ee
Merge pull request #461 from talex5/spawn
eio_posix: initial support for subprocesses
2023-03-20 12:28:44 +00:00
Thomas Leonard
970833b393 Update some comments 2023-03-20 10:46:38 +00:00
Thomas Leonard
d10a720134 Have fork actions call _exit directly on error
This simplifies the code a bit.

Also, check the type of the function passed to `Val_fork_fn`.
2023-03-20 10:35:20 +00:00
Thomas Leonard
16ec3592e8 Pass Rcfd to Fork_action.fchdir
We can operate directly on the safe wrapper, which also simplifies the
code a bit.

Also, add a test for `fchdir`.
2023-03-20 10:15:37 +00:00
Thomas Leonard
d105bd31a3 Update doc comment example to new function name 2023-03-20 10:02:56 +00:00
Thomas Leonard
56da38f4f4 Move Eio_posix.Process inside Low_level
Also, make `Process.t` abstract and document that `execve` can only go
last.

Suggested by Patrick Ferris.
2023-03-16 16:34:14 +00:00
Thomas Leonard
3e8f8d689d Fix infinite loop where waitpid returns 0 instead of ECHILD 2023-03-16 16:26:55 +00:00
Christiano Haesbaert
fb97c16cf6 Use calloc for safety 2023-03-16 15:58:11 +00:00
Thomas Leonard
0bec10cd86 eio_posix: initial support for subprocesses
To spawn a child executable on Unix, the parent `fork`s a copy of
itself, then has the child copy set up the environment and `execve` the
new program.

However, we cannot run any OCaml code in the forked child process. This
is because `fork` only duplicates its own domain. To the child, it
appears that all other domains have stopped responding and if it tries
to e.g. perform a GC then the child process will hang.

Therefore, the `fork` and all child actions need to be written in C.
To keep this flexible, this PR adds a new `fork_action` type and lets
the user provide a list of such actions to perform.
2023-03-15 11:34:55 +00:00
Thomas Leonard
eef510c7d8
Merge pull request #460 from talex5/fork-seq
Add Fiber.fork_seq
2023-03-14 09:39:52 +00:00
Thomas Leonard
9d968dc893 Conflict with seq < 0.3
Older versions conflict with the stdlib one. Also, require mdx 2.2.0 to
prevent complains about semi-colons.
2023-03-13 16:51:05 +00:00
Thomas Leonard
39c981b03f Add Fiber.fork_seq 2023-03-12 11:07:37 +00:00
Thomas Leonard
1b198cc539
Merge pull request #458 from talex5/eio-posix
Mention eio_posix in the documentation
2023-03-09 09:21:40 +00:00
Thomas Leonard
0f1bea6f53 Mention eio_posix in the documentation
Needed for #456.
2023-03-08 10:17:43 +00:00
Thomas Leonard
b0f80ccaff
Merge pull request #457 from patricoferris/dune.3.7.0
Use dune.3.7.0
2023-03-08 09:00:05 +00:00
Patrick Ferris
569ac31a1c Use dune.3.7.0 2023-03-08 00:17:32 +00:00
Thomas Leonard
146d246f23
Merge pull request #448 from talex5/eio-posix
Initial Eio_posix backend
2023-03-07 15:50:18 +00:00
Christiano Haesbaert
8118c1f500 Remove EINPROGRESS from do_nonblocking
It's only returned for `connect`, which does its own thing.
2023-03-07 14:59:43 +00:00
Thomas Leonard
0a4b3f98e8
Merge pull request #454 from talex5/pool-example
Improve worker pool example
2023-03-07 14:20:45 +00:00
Vesa Karvonen
8164a1385c Ensure standard streams are unique
Doesn't matter too much, but might be useful if someone wants to compare
them for equality.
2023-03-07 14:07:44 +00:00
Thomas Leonard
35f4be5a59 Improve worker pool example
- Use `fork_daemon` to avoid needing to `raise Exit` to finish.
- Use a 0-capacity stream to avoid running cancelled jobs and to benefit
  from the lock-free stream optimisations.
2023-03-07 13:48:22 +00:00
Thomas Leonard
08edcc107f
Merge pull request #455 from talex5/docs
Update README
2023-03-07 13:47:59 +00:00
Christiano Haesbaert
75762976da Lower poll_maxi when we can
We inflate maxi to account the highest index we're tracking, if we're about to
unregister the highest one, find the new highest.
2023-03-07 12:28:48 +01:00
Christiano Haesbaert
2c90835cdb Fix missing ; in fs.md
With this tests pass in eio-posix for me.
2023-03-07 12:28:48 +01:00
Christiano Haesbaert
6939cd1803 It's ok to include stdlib unconditionally
While here re-arrange include order, sys; std; caml.
2023-03-07 12:28:48 +01:00
Thomas Leonard
4cc9e1360e Clarify purpose of epoll FD test 2023-03-07 10:27:11 +00:00
Christiano Haesbaert
eab55876f3 Clean up C stubs 2023-03-07 10:23:01 +00:00
Thomas Leonard
36c970fb62 Link to API docs at the end of the tutorial 2023-03-06 16:36:18 +00:00
Thomas Leonard
741ea5a8f6 Link to #eio Matrix chat room 2023-03-06 16:35:17 +00:00
Thomas Leonard
dba1d7890b Note that gemini-eio no longer uses Angstrom 2023-03-06 16:30:08 +00:00
Thomas Leonard
cda2efc6c3 Update Awesome Multicore OCaml URL 2023-03-06 16:29:16 +00:00
Thomas Leonard
3f3aab4338 Improve error handling creating sockets and pipes
If e.g. `socketpair` fails, we now return the exception to the calling
fiber rather than aborting the event loop. Also, attach FDs to switches
before setting non-blocking mode, in case that fails (requested by Anil
Madhavapeddy).
2023-03-06 14:29:04 +00:00
Thomas Leonard
4b58f8b53f Fix comment about read_dir 2023-03-06 11:49:56 +00:00
Thomas Leonard
a696a23742 Only depend on eio_luv on Windows 2023-03-06 11:49:56 +00:00
Thomas Leonard
e2eeba0ec4 Avoid memory leak if caml_stat_strdup raises 2023-03-06 11:11:31 +00:00
Thomas Leonard
3b0ae1352f Ensure errno isn't changed by caml_stat_free
Some older compilers can do this.

Suggested by Anil Madhavapeddy <anil@recoil.org>.
2023-03-06 11:07:12 +00:00
Anil Madhavapeddy
e19bc4a912 eio_posix: do not include sys/random.h on non-Linux systems
It is not used by arc4random callers (that's stdlib.h) and there
is no sys/random.h on OpenBSD.
2023-03-05 15:54:11 +00:00
Thomas Leonard
49267689ef Initial Eio_posix backend
Co-authored-by: Christiano Haesbaert <haesbaert@haesbaert.org>
2023-03-02 15:26:08 +00:00
Thomas Leonard
e1e7cbe6b3
Merge pull request #452 from talex5/ci
Add CI for macOS
2023-02-28 13:19:53 +00:00
Thomas Leonard
d30f20908e Add CI for macOS 2023-02-28 12:06:37 +00:00
Thomas Leonard
fd33336786
Merge pull request #451 from talex5/more-tests
Add tests for pread, pwrite and readdir
2023-02-27 17:05:34 +00:00
Thomas Leonard
76ffd669ef Add test for readdir with no access
Report the error as `Permission_denied`.
2023-02-27 16:50:16 +00:00
Thomas Leonard
14fd63a5c8 Add test for pread and pwrite 2023-02-27 15:26:50 +00:00
Thomas Leonard
554179071b
Merge pull request #447 from talex5/eio-main
eio_main: make EIO_BACKEND handling more uniform
2023-02-24 09:50:49 +00:00
Thomas Leonard
27cf787c09 eio_main: make EIO_BACKEND handling more uniform
Previously, we only looked at this environment variable when we had both
eio_linux and eio_luv available. That could be confusing because if
eio_linux wasn't available and you asked for it then it would just use
eio_luv. Now we look at it in all cases.

Also accept "linux" as an alias of "io-uring", as that is the name of
the backend.
2023-02-23 15:39:13 +00:00
Thomas Leonard
e858a8cfd7
Merge pull request #443 from talex5/hacking
Add HACKING.md with hints for working on Eio
2023-02-22 12:07:59 +00:00
Thomas Leonard
e332fdbfec Add HACKING.md with hints for working on Eio 2023-02-20 17:38:53 +00:00
Thomas Leonard
4f0b331a8d
Merge pull request #442 from talex5/fix-tests-env
Tell dune about EIO_BACKEND
2023-02-20 16:06:22 +00:00
Thomas Leonard
4a15c8e2b7 Tell dune about EIO_BACKEND
If this changes, dune needs to re-run the tests.
2023-02-20 15:52:38 +00:00
Christiano Haesbaert
45efcc4a57
Merge pull request #441 from talex5/cloexec
eio_linux: add some missing close-on-execs
2023-02-14 10:12:14 +01:00
Thomas Leonard
32a30889b8 eio_linux: simplify connect code 2023-02-14 09:01:34 +00:00
Thomas Leonard
0ca5355385 eio_linux: fix missing close-on-exec flag for sockets 2023-02-14 08:59:26 +00:00
Christiano Haesbaert
f9b292452c
Merge pull request #440 from talex5/safe-fds
eio_linux: make it safe to share FDs across domains
2023-02-13 22:03:22 +01:00
Thomas Leonard
1c95a7395e eio_linux: make it safe to share FDs across domains
It was previously not safe to share file descriptors between domains.
The `fd_sharing.md` test demonstrates the problem: one domain enqueues
an operation using an FD and another domain closes it first.

`Eio_unix.Private.Rcfd` provides a ref-counted wrapper for file
descriptors to fix this problem. It's lock-free and designed to be fast
in the common case where an FD is only used from one domain, but safe in
the case where it's used from several.

The `bench_fd` benchmark is, if anything, slightly faster with this
change, though I don't know why.

There is a dscheck test that tests Rcfd with all combinations of two
users and two closers.

It includes an (unsafe) `peek` operation, as the public API already
provided this. We might want to remove that at some point and provide a
safe `Eio_unix.FD.use` operation instead.

There is support here for safely having two domains try to close an FD
at once, although eio_linux itself still doesn't support that (you must
close from the domain that opened it).

It could also allow us to support a `try_close` operation at some point.

Note that closing is now always done with `Unix.close`, not uring.
Using uring previously avoided races in the single-domain case, but
that's not necessary now that we have a general solution. I did have a
`remove_or_close` function that would use uring in the common case,
but `Unix.close` if an operation was in progress, but that seemed
unnecessary.
2023-02-09 13:58:02 +00:00
Thomas Leonard
53b35c0418
Merge pull request #439 from talex5/bench-fd
Add a benchmark for reading from /dev/zero
2023-02-09 12:02:58 +00:00
Thomas Leonard
3eb6806160 Add a benchmark for reading from /dev/zero 2023-02-09 11:58:45 +00:00
Thomas Leonard
5b63eb3eb5
Merge pull request #438 from talex5/fix-read-exact
eio_linux: read_exactly fails to update file_offset
2023-02-08 14:51:11 +00:00
Thomas Leonard
be59066fda eio_linux: read_exactly fails to update file_offset
If a read operation returns a short read then eio_linux automatically
retries. It updated the offset in the buffer, but not the file offset.
This only matters if a file offset is being used.
2023-02-08 14:36:56 +00:00
Thomas Leonard
4ce67773e7
Merge pull request #436 from talex5/doc-signals
Add more Conditions documentation
2023-02-06 15:39:08 +00:00
Thomas Leonard
3a6505fb0f Add more Eio.Condition documentation
This adds a discussion of conditions to the README and provides examples
using them to handle signals.
2023-02-06 14:19:15 +00:00
Thomas Leonard
38c337e888
Merge pull request #437 from talex5/release
Update changelog
2023-02-05 15:41:18 +00:00
Thomas Leonard
eeed0fe6d5 Update changelog 2023-02-05 15:26:09 +00:00
Christiano Haesbaert
388f90689f
Merge pull request #432 from talex5/fix-other-archs
Fix various architectures
2023-02-01 17:16:46 +01:00
Thomas Leonard
2fa5bc474f Work around dune %{system} bug
Dune has various other odd names for Linux:
https://github.com/ocaml/dune/issues/4895
2023-02-01 11:38:48 +00:00
Thomas Leonard
97d3118377 eio_luv: fix max_luv_buffer_size on 32-bit platforms
OCaml ints are on bit shorter than C ints.
2023-02-01 11:36:53 +00:00
Thomas Leonard
d726c00cd1
Merge pull request #430 from talex5/release
Add missing test-dependency on MDX
2023-02-01 10:56:27 +00:00
Thomas Leonard
17860ca0ce Add missing test-dependency on MDX 2023-02-01 09:58:20 +00:00
Thomas Leonard
528f6a7b6d
Merge pull request #429 from talex5/release
Prepare release
2023-02-01 09:31:45 +00:00
Thomas Leonard
18447c334f Prepare release 2023-01-31 16:22:25 +00:00
Thomas Leonard
b83534e400
Merge pull request #428 from talex5/uring-submit
eio_linux: call submit as needed
2023-01-31 16:14:14 +00:00
Thomas Leonard
56c4d49dd9 eio_linux: call submit as needed
We may fail to submit a job because the SQE queue is full. Previously
we would wait until some existing request completed, but that might
never happen. Instead, we just flush the SQE queue and retry.

Fixes #409.
2023-01-31 11:21:16 +00:00
Thomas Leonard
789c6d2c52
Merge pull request #418 from adatario/fix_uint48
Buf_write: Fix BE.uint48 and LE.uint48.
2023-01-31 09:30:08 +00:00
Thomas Leonard
931b8ef608
Merge pull request #426 from patricoferris/patch-3
Describe secure_random as an infinite source
2023-01-30 17:24:08 +00:00
Patrick Ferris
050953431f
Describe secure_random as an infinite source 2023-01-30 16:32:29 +00:00
Adarsh Amirtham
931be240ee Buf_write: Fix BE.uint48 and LE.uint48.
Arguments to logical shift are provided in bits, not bytes.
Also, the BE version mixed up the 32-bit and 16-bit sizes.
Note: this bug also affects Faraday.

Co-authored-by: Thomas Leonard <talex5@gmail.com>
2023-01-30 15:15:20 +00:00
Thomas Leonard
3a6dea84e8 Add Eio.Buf_read.uint8
This is to match `Buf_write.uint8`.
2023-01-30 15:13:55 +00:00
Thomas Leonard
a36443f48a
Merge pull request #421 from talex5/fix-luv-leaks
eio_luv: fix some resource leaks
2023-01-30 12:16:54 +00:00
Thomas Leonard
2009ba15bb eio_luv: fix some resource leaks
Previously, we asked to close the `async` notifier after the loop had
stopped. Presumably because the loop wasn't running, this close
operation never ran, which leaked some FDs. Also, the `Loop.close` it
was supposed to call after closing itself never got called either.

Once that was fixed, `Loop.close` reported further resource leaks in
`Poll`, `Process`, `sleep_ms` and `domain_mgr`, which this commit also
fixes.

Fixes #419.

Co-authored-by: Patrick Ferris <patrick@sirref.org>
2023-01-30 11:14:28 +00:00
Thomas Leonard
4d645a5c09
Merge pull request #417 from talex5/network-examples
Update network examples to use run_server
2023-01-30 11:06:58 +00:00
Thomas Leonard
d35eb8d636 Update network examples to use run_server
Since this server now accepts multiple connections, we need to exit when
the client is done. I switched the order of the messages so that the
client reading the reply is the last thing that happens, to avoid a
race here.
2023-01-30 11:01:08 +00:00
Thomas Leonard
5fd9608671
Merge pull request #420 from talex5/sigpipe
Windows: fix "unavailable signal" error
2023-01-30 10:46:10 +00:00
Thomas Leonard
4839a47c4b eio_linux: delay setting SIGPIPE handler
This matches the new eio_luv behaviour.

Also, added a test.
2023-01-30 10:44:53 +00:00
Thomas Leonard
1033238d11 Windows: fix "unavailable signal" error
Only try to ignore sigpipe signals on Unix-type systems. Otherwise, it
fails with:

    Fatal error: exception Invalid_argument("Sys.signal: unavailable signal")

Also, delay doing this until `Eio_luv.run` is called so merely linking
with `Eio_luv` doesn't change the behaviour of your program.

Reported-by: Nicolás Ojeda Bär
2023-01-30 10:44:53 +00:00
Thomas Leonard
6f31c365f0
Merge pull request #423 from talex5/fix-eventfd-pool
eio_linux: fix thread-safety bug in EventFD_pool
2023-01-30 10:44:29 +00:00
Thomas Leonard
3731fd0de7 eio_linux: fix thread-safety bug in EventFD_pool
It was using Lf_queue, but that's a single-consumer queue. The eventfd
pool is used by multiple consumers.

I suspect this is the cause of the occasional `Exception: End_of_file`
errors seen in CI. Two domains read the same node, but one of them sees
the zeroed-out value and tries to read from stdin.
2023-01-30 09:59:28 +00:00
Thomas Leonard
80f5352526
Merge pull request #408 from bikallem/run_server_multicore
net: add run_server to run eio servers
2023-01-26 11:55:13 +00:00
Bikal Lem
1b6bab583d net: add run_server
Runs an accept loop in one or more domains, with cancellation and
graceful shutdown, and an optional maximum number of concurrent
connections.

Co-authored-by: Thomas Leonard <talex5@gmail.com>
2023-01-26 11:48:26 +00:00
Bikal Lem
f455ade7f6 net: define type connection_handler 2023-01-26 11:27:58 +00:00
Thomas Leonard
f707dceaff
Merge pull request #413 from talex5/sync
Add cancellable lock-free synchronous channel
2023-01-26 11:23:30 +00:00
Thomas Leonard
9b128864a0 Add cancellable lock-free synchronous channel
This is used when you ask for an Eio.Stream with capacity 0. It's
slightly slower in the single-domain case, but much faster with multiple
domains.

I originally planned to write a stream replacement that worked for all
capacities, but making a single data structure work for both zero and
non-zero capacities turned out to be difficult, especially with
non-blocking take.

For a non-blocking take on a non-zero capacity stream you have the
advantage that you can reserve one of the items already in the stream,
or fail if there isn't one. But with a zero capacity stream a
non-blocking take always involves both parties. But zero capacity
streams also offer simplifications.

The algorithm is described in detail in the comment at the top of
sync.ml. `lib_eio/tests/sync.md` contains a walk-through of some
(non-racing) cases, showing the internal state of the stream at each
step. `lib_eio/tests/dscheck/test_sync.ml` tests all possible
interleavings of atomic operations.

I simplified the old locking version in `stream.ml` as it no longer
needs special cases for `capacity=0`.
2023-01-25 21:05:55 +00:00
Thomas Leonard
60122e8b1b
Merge pull request #414 from smondet/smondet-fix-luv-process-test
eio-luv: improve `process.md` test
2023-01-25 10:01:03 +00:00
Sebastien Mondet
3a149696bd eio-luv: improve process.md test 2023-01-24 18:06:20 -05:00
Thomas Leonard
c634c4f7d5
Merge pull request #412 from haesbaert/luvsighack
Fix luv signals (issue #400)
2023-01-24 12:06:29 +00:00
Christiano Haesbaert
aa1a5c33c0 Fix luv signals (issue #400)
This makes sure we can process signals in luv the same way we do in uring. As
stated in #400 the main issue is that luv's mainloop will restart on EINTR and
we will never unwind back to ocaml land, so even though the process got the
signal, the runtime will not see it until something else goes on.

The trick here is to abuse POSIX thread semantics and shove all signals into one
specific systhread by blocking them in the other threads.

Additionally, I've fixed a bug in OCaml 5.0 where the systhreads end up starting
with all signals blocked: https://github.com/ocaml/ocaml/pull/11880. This PR
works even with/without the bug.

Danger Zone
~~~~~~~~~~~
This is tricky ! If you're thinking we can do better than pipes, think again !
Unix.sigsuspend doesn't work on multithreaded, we don't have a real Unix.pause
(it's implemented on top of sigsuspend !).
The semantics for kill(2) to "self" are different than "from outside" when
multithreaded, and the runtime doesn't expose pthread_kill(2). That's why on the
test we have to fork and signal back to the parent. I know, it's horrible.

Co-authored-by: Thomas Leonard <talex5@gmail.com>
2023-01-24 12:01:39 +00:00
Thomas Leonard
edded87715
Merge pull request #411 from talex5/bench
Improve stream benchmark
2023-01-23 15:18:36 +00:00
Thomas Leonard
fa9987a80b Improve stream benchmark
Previously the multi-domain test only had one producer and one consumer.
It now tries with various combinations up to 4 producers with 4
consumers. Each producer also submits items from many fibers at the same
time. This is more like a typical use of a stream, where IO domains are
handling many requests by submitting CPU-intensive jobs to a pool of
batch processing domains.

It now also includes some spinner fibers, to avoid measuring domain
resume time. That previously dominated the results (though it happens
less anyway now that the senders run multiple fibers).
2023-01-23 14:02:01 +00:00
Thomas Leonard
494d1d12fb
Merge pull request #410 from talex5/dune3
Update to Dune 3
2023-01-23 09:38:11 +00:00
Thomas Leonard
b81bd0d97a Update to Dune 3
Allows use of dependencies in MDX rules (which will be useful later).
2023-01-22 16:07:16 +00:00
Thomas Leonard
80f1486203
Merge pull request #401 from talex5/lock-free-promise
Make Eio.Promise lock-free
2023-01-09 10:58:17 +00:00
Thomas Leonard
e93a042705 Make Eio.Promise lock-free
This is slightly faster and simplifies the code.
2023-01-09 10:47:04 +00:00
Thomas Leonard
a62f7b8f54 Improve promise benchmark
Include a test where the domains never sleep, to avoid simply
benchmarking time to wake a domain.
2023-01-09 10:47:04 +00:00
Thomas Leonard
e2d014fa70
Merge pull request #403 from talex5/debug-logging
Remove debug-level logging
2023-01-06 19:38:30 +00:00
Thomas Leonard
dfa3076259
Merge pull request #399 from Cjen1/add-bigstringaf-ser
Add Buf_read.BE/LE parsers
2023-01-06 16:46:26 +00:00
Thomas Leonard
5407ed68c8
Merge pull request #404 from talex5/fix-fuzzing
Fix off-by-one in fuzz test
2023-01-06 16:19:26 +00:00
Cjen1
9998c86dc1 Add big and little endian parsers 2023-01-06 15:38:01 +00:00
Thomas Leonard
33b27ce0db Fix off-by-one in fuzz test
Broken in 10e8a0e810. `Astring.String.with_index_range`'s end is
inclusive.
2023-01-06 15:26:01 +00:00
Thomas Leonard
b797a9d911 Remove debug-level logging
The debug logging doesn't seem useful and we also have tracing anyway.
It clutters the logs when people turn on debug-level logging for their
application.
2023-01-06 15:12:26 +00:00
Thomas Leonard
670e656560
Merge pull request #402 from talex5/astring
Remove test dependency on Astring
2023-01-06 14:47:26 +00:00
Thomas Leonard
10e8a0e810 Remove test dependency on Astring
The stdlib now provides most of what we need and we can implement the
rest manually.
2023-01-06 14:10:25 +00:00
Thomas Leonard
32c26ab313
Merge pull request #398 from talex5/lock-free-semaphore
Make Eio.Semaphore lock-free
2023-01-04 17:47:54 +00:00
Thomas Leonard
97acd4b611 Make Eio.Semaphore lock-free
This uses the new Cells module to replace the use of a mutex.

Co-authored-by: Vesa Karvonen <vesa.a.j.k@gmail.com>
2023-01-04 15:19:32 +00:00
Thomas Leonard
79df549785 Improve semaphore benchmark
Before, two domains just passed control back and forth, which was really
just timing how long it takes to wake a domain.

Now, we keep the domains busy while waiting for the semaphore. We also
now have 4 domains trying to use 2-4 resources, which is a more
realistic use of a semaphore.
2023-01-03 13:41:14 +00:00
Thomas Leonard
62568f7895
Merge pull request #397 from talex5/lock-free-condition
Make Eio.Condition lock-free
2023-01-03 11:29:44 +00:00
Thomas Leonard
033682ffa0 Lock-free Eio.Condition
This makes Eio.Condition safe to use from a signal handler (or GC
finalizer).

The cells.ml abstraction should be useful for several other
synchronisation primitives (see https://arxiv.org/pdf/2111.12682.pdf).
2023-01-03 11:27:22 +00:00
Thomas Leonard
589320eb00
Merge pull request #396 from talex5/simplify-cancel
Simplify cancellation logic
2022-12-21 16:46:35 +00:00
Thomas Leonard
036d83db62 Simplify cancellation logic
Before, the fiber context held an atomic recording whether the operation
had succeeded or been cancelled, to ensure that only one of these things
happened. However, this doesn't need to be atomic for single-threaded
operations, and even for parallel operations, we might perfer to keep
the atomic state elsewhere.

Now, operations just set a regular function and it's up to the operation
to ensure it's atomic if needed.

This is needed to implement lock-free waiters. Otherwise we have this
problem:

1. Domain 1 starts waiting for something from another domain.
2. Domain 2 prepares to provide a value to it.
3. Domain 1 cancels the operation and continues.
4. Domain 1 starts a new operation with a new cancel function.
5. Domain 2 sees the operation isn't cancelled and sends the value.

This isn't a problem when using a lock because both domains take the
lock. Currently, the waiters module still uses a lock (but now
provides its own mutex), and so works the same as before.

Some other related improvements:

- In eio_linux, we no longer wait for the cancel response before continuing.

  Before, cancelling an io_uring request would wait for the result of
  the cancellation before the cancel operation returned. But this isn't
  very useful, makes cancelling lots of things very slow since you can
  only do one at a time, and doesn't work with the new cancellation
  system where you can't switch fibers while cancelling.

- The "Cancelling multiple jobs" test has been removed. It was testing
  what happens when a cancel function switches fibers, but this is no
  longer allowed.

- In eio_luv, we only report cancellation if the operation didn't succeed.

  This changes the signature of `Low_level.await_with_cancel` slightly,
  since it now needs to know whether the operation was successful or not.

- Moved the cancellation logic into the domain manager implementation.
  This was the only place where a fiber could have a cancel function
  while not suspended.
2022-12-21 14:17:03 +00:00
Thomas Leonard
c4b0ef2c97 Benchmark conditions 2022-12-21 12:17:25 +00:00
Thomas Leonard
82886514b7
Merge pull request #395 from bikallem/eio-time-utc
time: clarify Eio.Time.now is UTC
2022-12-20 16:46:39 +00:00
Bikal Lem
16f79c89ca time: clarify Eio.Time.now is UTC 2022-12-20 16:40:15 +00:00
Thomas Leonard
335c57504f
Merge pull request #393 from talex5/docker-ocaml5
Simply Dockerfile now OCaml 5 is out
2022-12-20 11:13:53 +00:00
Thomas Leonard
ff116e8158 Simply Dockerfile now OCaml 5 is out
We no longer need to compile the compiler.
2022-12-20 10:14:08 +00:00
Thomas Leonard
8a72e64644
Merge pull request #394 from talex5/warn-first
Add a warning to the tutorial about Fiber.first
2022-12-20 10:13:40 +00:00
Thomas Leonard
189961b12f Add a warning to the tutorial about Fiber.first 2022-12-17 20:01:14 +00:00
Thomas Leonard
56ea1cd667
Merge pull request #391 from talex5/ocaml5
Update README for OCaml 5 release
2022-12-16 14:18:17 +00:00
Thomas Leonard
eeb596f20d Update README for OCaml 5 release 2022-12-16 13:59:21 +00:00
Thomas Leonard
ccb1373b32
Merge pull request #389 from talex5/docs
Documentation improvements
2022-12-15 16:40:53 +00:00
Thomas Leonard
51e2029322 Link to current status GitHub issue
It's easier to track progress in an issue rather than in the README.
2022-12-14 11:19:11 +00:00
Thomas Leonard
e277fcef6a Add some example programs
- `./examples/hello` is a the hello-world tutorial example
- `./examples/net` is a more complete networking example

Some people may prefer to edit these example projects rather than using
utop for everything.
2022-12-14 11:19:11 +00:00
Thomas Leonard
50abf52dd3
Merge pull request #384 from talex5/ocaml5-rc1
Update README for OCaml 5.0.0~rc1
2022-12-09 12:44:44 +00:00
Thomas Leonard
aa375c91be Update README for OCaml 5.0.0~rc1 2022-12-09 12:42:44 +00:00
Thomas Leonard
c987f99f42
Merge pull request #385 from bikallem/mtime-deprecation
time: Mtime.Spand.to_s has been deprecated in mtime 2.0.0
2022-12-09 11:11:58 +00:00
Bikal Lem
0d11d199c0 time: Mtime.Spand.to_s has been deprecated in mtime 2.0.0 2022-12-09 10:22:15 +00:00
Thomas Leonard
90de1d4509
Merge pull request #381 from talex5/wakeup
Make Eio_linux.wakeup signal-safe
2022-12-08 09:52:37 +00:00
Thomas Leonard
1233ceaebe Make Eio_linux.wakeup signal-safe
This will eventually allow Waiters to be used from signal handlers and
GC finalizers (once that's lock-free too).

Previously, `wakeup` couldn't be used from a signal handler because it
took a lock (to ensure that the eventfd wasn't closed while it wrote to
it). Now, we avoid ever closing eventfds and simply keep free ones in a
pool.
2022-12-07 19:44:35 +00:00
Thomas Leonard
aa91a7bf81
Merge pull request #383 from talex5/release
Prepare release
2022-12-07 11:40:49 +00:00
Thomas Leonard
d626fe0092 Prepare release 2022-12-07 11:38:27 +00:00
Thomas Leonard
3c4bca83f1
Merge pull request #379 from talex5/readdir-docs
Document that read_dir excludes '.' and '..'
2022-12-05 13:51:52 +00:00
Thomas Leonard
7670c6a7e9 Document that read_dir excludes '.' and '..' 2022-12-05 13:06:13 +00:00
Thomas Leonard
a17ec4480c
Merge pull request #378 from talex5/errors
Unify IO errors as Eio.Io
2022-12-05 13:03:19 +00:00
Thomas Leonard
bef84ce78e Unify IO errors as Eio.Io
This makes it easy to catch and log all IO errors if desired. The
exception payload gives the type and can be used for matching specific
errors. It also allows attaching extra information to exceptions.

When multiple exceptions occur, we now keep and report all the backtraces.
If multiple IO exceptions occur, the result is also an IO exception (so
you can still catch all IO errors).

Various functions now add extra context to IO exceptions:
- `accept_fork` adds the remote address.
- Various functions add their arguments (`Net.connect`, `Path.load`, etc).

Also:

- `Eio_linux.openfile` is gone (was just a hack for testing in the early
  days).

- `connect` functions no longer need to wrap all errors in
  `Connection_failure`. The fact you got an IO error from `connect` is
  enough.

- Fixed non-tail-recursive continue in Eio_luv (`Socket_of_fd`).
2022-12-01 15:20:00 +00:00
Christiano Haesbaert
e2e94d718f
Merge pull request #377 from talex5/fix-example
Use a proper HTTP response in the README example
2022-12-01 10:28:41 +01:00
Thomas Leonard
7bd0b666f6 Use a proper HTTP response in the README example
Was missing the version part.

Also, clarify `yield` comment.
2022-11-30 11:50:39 +00:00
Thomas Leonard
eb4d9edcc2
Merge pull request #375 from talex5/beta2
Update README for OCaml 5.0.0~beta2
2022-11-30 10:58:14 +00:00
Thomas Leonard
29a2afee0b Update README for OCaml 5.0.0~beta2 2022-11-29 14:24:59 +00:00
Thomas Leonard
683e8dda92
Merge pull request #359 from patricoferris/eio-luv-process
eio_luv: add low-level process support
2022-11-23 11:14:01 +00:00
Patrick Ferris
de4532177a Add low-level process support to eio_luv 2022-11-23 10:56:58 +00:00
Thomas Leonard
f30025cc99 Expose Eio_luv.Low_level.Stream.write 2022-11-23 10:53:13 +00:00
Thomas Leonard
d24af9a534
Merge pull request #371 from talex5/get_loop
Expose Eio_luv.Low_level.get_loop
2022-11-21 10:43:29 +00:00
Thomas Leonard
9d1cafee57 Expose Eio_luv.Low_level.get_loop
This is needed if you want to create resources directly and then use
them with Eio_luv.
2022-11-17 10:30:40 +00:00
Thomas Leonard
35feb3aaa8
Merge pull request #338 from talex5/clocks3
Add Time.Mono for monotonic clocks
2022-11-15 11:21:25 +00:00
Thomas Leonard
ce4b39243b Deprecate Timeout.of_s
Timeouts should be using a monotonic clock.
2022-11-15 11:14:47 +00:00
Thomas Leonard
42f2a315f3 Add Time.Mono for monotonic clocks
Based on PR #308 by Bikal Lem.
2022-11-15 11:11:38 +00:00
Thomas Leonard
11011dd8bc
Merge pull request #369 from talex5/backtrace
Don't lose backtrace in Switch.run_internal
2022-11-15 11:03:01 +00:00
Thomas Leonard
2014263e36 Don't lose backtrace in Switch.run_internal 2022-11-10 12:39:48 +00:00
Bikal Lem
a70865e8f0
Allow datagram_socket to be created without address (#360)
* net: allow to create udp socket without address

* udp: add udp test

* udp: allow Eio.Net.Sockaddr.t in datagram_socket() without casting

* udp: add reuse_addr and reuse_port to datagram_socket

* Simplify tests

Removed the last IPv6 test since it was identical to an earlier test.

* Simplify luv code

* Move socket options out of Eio_unix

* Simplify eio_linux code

Fix problem that UDP sockets were not created close-on-exec.

Co-authored-by: Thomas Leonard <talex5@gmail.com>
2022-11-04 11:44:57 +00:00
Christiano Haesbaert
5c1eae435f
Merge pull request #339 from haesbaert/initialsize
Expose File.stat and improve Path.load
2022-11-03 16:56:37 +01:00
Christiano Haesbaert
b7f87f1abb Expose File.stat and improve Path.load
Some notes:
  - Use Unix.Largestat instead of Unix.stat
  - Add a portable stat for the user
  - Moved Path.Unix_perm into File.Unix_perm to avoid cyclical ref

A previous attempt of this diff tried to make proper use of Luv.File.Stat but
there were multiple issues: `mode` was not properly exported so I had to add a
discovery.ml; it only works for a subset of file types and we had to manually
convert timestamps to float; all doable but an insuferable amount of code to end
up with less functionality than just calling into Unix.Largestat.stat.

There are still some kinks:
  - Luv will always follow a link, so we can't really stat on a symbolic link,
    this is because we rely on realpath(3) which resolves the link.
  - We can't open named pipes in with luv atm, so we also can't stat it (#357).
  - I did test all filemodes, but didn't add all to the test since they are
    impractical (opening /dev/tty for Character_special...)

--

Path.load was doubling the buffer and trying to fit enough until it saw an EOF.
This would cause many unecessary large allocations and my poor 16GB ram laptop
couldn't even load a 5GB file without getting OOMed.

This diff makes load create an initial buffer of the size of the file and load
exactly that amount of bytes in it.
2022-11-03 16:48:35 +01:00
Thomas Leonard
35aed19c63
Merge pull request #358 from talex5/warn-first
Warn about both operations succeeding in Fiber.first
2022-10-29 15:04:42 +01:00
Thomas Leonard
fe96fc1d03 Warn about both operations succeeding in Fiber.first
Closes #356.
2022-10-29 13:21:53 +01:00
Thomas Leonard
849391e3b4
Merge pull request #344 from haesbaert/getrandom
Fix getrandom(2)
2022-10-26 11:49:26 +01:00
Christiano Haesbaert
2601e44cff Don't ever return a short-count in secure_random
- Prevent users from ever seeing a short-count in secure_random, luv already
   did this.
 - Don't truncate the luv buffer, make subsequent calls to fill the rest.

(I still think it should be guaranteed as lowest as possible (in the stub)), but
this was the compromise made in the Peace Accords of Whereby, October of 2022 AD.

--

I didn't know much about getrandom(2), and I'm surprised it was designed
allowing short counts even though they based it off getentropy(2), which always
does the right thing.

"""
The behavior when a call to getrandom() that is blocked while reading from the
urandom source is interrupted by a signal handler depends on the initialization
state of the entropy buffer and on the request size, buflen.  If the entropy is
not yet initialized, then the call fails with the EINTR error.  If the entropy
pool has been initialized and the request size is large (buflen > 256), the call
either succeeds, returning a partially filled buffer, or fails with the error
EINTR.  If the entropy pool has been initialized and the quest size is small
(buflen <= 256), then getrandom() will not fail with EINTR.  Instead, it will
return all of the bytes that have been requested.re
"""
2022-10-26 12:19:45 +02:00
Christiano Haesbaert
232ec419eb
Merge pull request #355 from haesbaert/pipedup
Add Eio_unix.pipe workaround, zap Eio_linux.pipe
2022-10-25 13:34:04 +02:00
Christiano Haesbaert
3c6d07a63c Make pipes nonblocking in Eio_unix.pipe, zap Eio_linux.pipe
Make sure we use pipes as nonblocking see #319 in Eio_unix.pipe.
Zap Eio_linux.pipe since it's only used in tests.
2022-10-24 14:05:50 +02:00
Christiano Haesbaert
63f5896699
Merge pull request #327 from haesbaert/fixhang
Fix pipe hang (issue #319)
2022-10-21 19:37:34 +02:00
Christiano Haesbaert
13daa37c03
Merge pull request #353 from talex5/single-read
Rename `Flow.read` to `Flow.single_read`
2022-10-20 22:52:09 +02:00
Christiano Haesbaert
a702def32e Fix pipe hang (issue #319)
This issue turned out to be a kernel bug which has been fixed in:
46a525e199

Reported here:
https://github.com/axboe/liburing/issues/665#issuecomment-1262837042

We can workaround it by making sure pipes are non-blocking, uring considers
pipes unbounded work and relies on uring worker threads if they are blocking,
the bug is only triggered if this is the case, so force them to be non-blocking.

If we use splice, the splice call itself needs the worker threads and the bug
surfaces again (I verified this with perf probes), so disable splice for now.
Even with the fix, it's desirable to keep pipes as non-blocking to avoid thread
pooling.

The splice call can return EAGAIN in uring, this happens even with the kernel
patched, so handle it for the future.

We can tune this better by disabling splice only for the unpatched kernels.
2022-10-20 22:15:24 +02:00
Thomas Leonard
aedd11962c Rename Flow.read to Flow.single_read
This is a low-level function and it is easy to use it incorrectly by
ignoring the possibility of short reads. The name `Flow.read` made it
sound like a suitable default way to read from a flow.

See discussion in https://github.com/ocaml-multicore/eio/pull/344.
2022-10-20 19:46:25 +01:00
Thomas Leonard
243778792f
Merge pull request #350 from patricoferris/pipe
Add pipe to eio_unix
2022-10-20 15:51:44 +01:00
Patrick Ferris
d77826f096 Add pipe to eio_unix 2022-10-20 15:39:51 +01:00
Thomas Leonard
a638009f0d
Merge pull request #349 from talex5/mock-backtrace
Eio_mock.Backend: preserve backtraces from main
2022-10-20 15:06:15 +01:00
Thomas Leonard
bf0245a0f6 Eio_mock.Backend: preserve backtraces from main 2022-10-19 15:25:03 +01:00
Thomas Leonard
7cb0da336b
Merge pull request #348 from talex5/release
Also constrain versions of ocaml-variants and ocaml-system
2022-10-13 11:18:04 +01:00
Kate
485b348dc1 Also constrain versions of ocaml-variants and ocaml-system 2022-10-12 18:01:43 +01:00
Thomas Leonard
9faa07bb93
Merge pull request #347 from talex5/release
Prepare release
2022-10-12 17:48:37 +01:00
Thomas Leonard
75aa4475d3 Prepare release 2022-10-12 17:46:27 +01:00
Thomas Leonard
b749571487
Merge pull request #346 from talex5/beta1
Update to OCaml 5.0.0~beta1
2022-10-12 17:30:10 +01:00
Antonio Nuno Monteiro
ab7c76a6e5 Update to OCaml 5.0.0~beta1 2022-10-12 17:05:50 +01:00
Thomas Leonard
80036f3545
Merge pull request #343 from talex5/luv-limit-fix
Split the reads into no more than 2^32-1 for luv
2022-10-11 12:02:04 +01:00
Christiano Haesbaert
2e1142ee08 Split the reads into no more than 2^32-1 for luv
Luv uses a 32 bit int for buffer sizes and wraps if the value passed is
too big. In particular, a request for to read 4GB of data is interpreted
as a request for 0 bytes.

Fixes #335.
2022-10-11 11:48:42 +01:00
Christiano Haesbaert
0f9d577c66
Merge pull request #318 from haesbaert/fwrite
Implement Flow.write and write_from
2022-10-06 12:33:04 +02:00
Christiano Haesbaert
4995c2c828 Add Flow.write
This provides an optimised alternative to copy in the case where you are
writing from a buffer. The default implementation simply call copy.

Co-authored-by: Thomas Leonard <talex5@gmail.com>
2022-10-06 11:27:58 +01:00
Thomas Leonard
4c743baffe
Merge pull request #337 from talex5/readme
Minor README improvements
2022-10-04 15:21:10 +01:00
Thomas Leonard
56e663126e Minor README improvements
- Link to subprocess issue.
- Remove discussion of `fork_sub` from README. It's less useful now we
  have implicit cancellation.
2022-10-03 22:27:41 +01:00
Thomas Leonard
e9ad2b005e
Merge pull request #333 from talex5/ocaml-trunk
Update install instructions for OCaml trunk
2022-09-30 11:06:28 +01:00
Thomas Leonard
4b0473ba67 Update install instructions for OCaml trunk 2022-09-30 10:53:15 +01:00
Thomas Leonard
f1e18c3d8d
Merge pull request #302 from bikallem/with_connect
net: add Net.with_tcp_connect
2022-09-27 12:00:58 +01:00
Thomas Leonard
bd160c91c2 Update to new timeout system 2022-09-27 09:24:53 +01:00
Bikal Lem
b0afd4401e net: add Net.with_tcp_connect
`Net.with_tcp_connect` takes a domain-name/port/service and attempts to
establish connection to it. IP addresses for the host are tried one
after the other while preferring/trying IPv6 protocol addresses first
if available.

We also introduce two new exceptions:
1. Invalid_host - raised when a connection couldn't be established
  for any of the addresses defined for a given host/port/service.
2. Unix_error - introduced to mimic Unix.Unix_error without depending
  on "unix" library.
2022-09-27 09:03:35 +01:00
Thomas Leonard
3af12b9be2
Merge pull request #328 from talex5/mock-clock
Add Eio_mock.Clock
2022-09-27 09:03:08 +01:00
Thomas Leonard
5301f11a1e Add Eio_mock.Clock 2022-09-26 13:25:08 +01:00
Thomas Leonard
dd842fcbb4
Merge pull request #326 from anmonteiro/anmonteiro/use-log-src
[eio_main.linux.ml] Use dedicated log source
2022-09-25 09:44:53 +01:00
Antonio Nuno Monteiro
204f01044e [eio_main.linux.ml] Use dedicated log source 2022-09-24 22:19:04 -07:00
Thomas Leonard
c3431a079b
Merge pull request #324 from haesbaert/condrace
Condition.broadcast must interlock as well
2022-09-24 11:01:20 +01:00
Christiano Haesbaert
1580895e4c Condition.broadcast must interlock as well
There's a race where broadcast doesn't interlock and a in-flight waiter will
have a lost wakeup:

CPU0 at:
    Eio.mutex_lock mutex;
    check_condition -> maybe abort
    Mutex.lock t.mutex; (inlined await for visibility)
B:  Eio_mutex.unlock mutex;
---> HERE
    match Waiters.await ~mutex:(Some t.mutex) t.waiters t.id with
    | ()           -> Eio_mutex.lock mutex
    | exception ex -> Eio_mutex.lock mutex; raise ex

CPU1 at:
    Eio.Mutex.use_rw ~protect:false mutex (fun () -> foo := true)
---> HERE
    Waiters.wake_all t.waiters (); (inlined broadcast for visibility)

1 - CPU0:HERE already tested the condition, it's false so it's ready to sleep,
    it interlocked with the runtime Mutex and released the Eio one.
2 - CPU1 sets the condition just after CPU0:B and now drains the queue and
    wakes the waiters, but CPU0 didn't make the waiting list yet, so it loses
    the wakeup.
3 - CPU0 waits forever.

The fix is to lock t.mutex, we don't need the full dance:
LOCK(eio_mutex)->LOCK(mutex)->UNLOCK(eio_mutex), just locking the
inner mutex is enough.

disclaimer: It's late and I might have missed something.
2022-09-24 10:55:20 +01:00
Thomas Leonard
a5f09f7a8c
Merge pull request #320 from talex5/timeout
Add Eio.Time.Timeout
2022-09-22 10:12:05 +01:00
Thomas Leonard
b49e7088e6 Add Eio.Time.Timeout
This may be useful for passing timeouts as arguments.
2022-09-20 10:32:56 +01:00
Thomas Leonard
97248d9cf8
Merge pull request #311 from talex5/cancel-connect
eio_luv: allow Net.connect to be cancelled
2022-09-20 10:11:53 +01:00
Thomas Leonard
9cf532b99e eio_luv: allow Net.connect to be cancelled
Fixes #303.

Reported by Nicolás Ojeda Bär.
2022-09-20 10:10:32 +01:00
Thomas Leonard
bdf1753e7a
Merge pull request #313 from haesbaert/socketpair
Account for stack differences in the socketpair test (issue #312)
2022-09-20 09:28:48 +01:00
Thomas Leonard
8be92c19a9
Merge pull request #317 from c-cube/flow-source-string
direct implementation of Flow.source_string
2022-09-20 09:27:01 +01:00
Christiano Haesbaert
c4887ec073 Account for stack differences in the socketpair test (issue #312)
Linux returns ECONNRESET while every other stack tested returns EOF on this
test.

I did not dig into the especification but Linux appears to be on the weird side.

In order to get a ECONNRESET, the write needs to hit the socket _after_ the
close, but our program does:

WRITE(A)
CLOSE(B)
READ(A)

Linux never gives "A" side a chance to see a proper EOF, while the others do:

Linux        -> ECONNRESET
FreeBSD 12   -> EOF
OpenBSD 7.2  -> EOF
macos 12.5.1 -> EOF

Until we can reliably guarantee the same return on all stacks, relax the test a
bit and accept EOF.
2022-09-17 21:01:04 +02:00
Simon Cruanes
4213c19d61 Direct implementation of Flow.string_source 2022-09-17 17:18:18 +01:00
Thomas Leonard
5a77787a1b
Merge pull request #307 from nojb/seek
Add API for seekable read/writes
2022-09-17 16:33:01 +01:00
Thomas Leonard
5be0ec41d5 Make Eio_luv.Low_level.write return a result
For consistency with the rest of the API.

Spotted by Christiano Haesbaert.
2022-09-17 16:21:26 +01:00
Thomas Leonard
4c8a7646dd Split file operations into new Eio.File module
Also, use `pwrite_single` and `pwrite_all` for clarity, and don't treat
a 0-length write as `End_of_file`.
2022-09-15 17:21:11 +01:00
Nicolás Ojeda Bär
831995c552 Add Fs.pread, Fs.pread_exact, Fs.pwrite, Fs.pwrite_exact 2022-09-14 19:46:03 +02:00
Thomas Leonard
995e255965
Merge pull request #309 from bikallem/buf_read
Buf_read: Add take_while1 and skip_while1
2022-09-13 15:20:45 +01:00
Thomas Leonard
1bb29075a8
Merge pull request #315 from raphael-proust/move-list-ops-in-dedicated-submodule
Move list functions into a dedicated submodule
2022-09-13 15:18:51 +01:00
Thomas Leonard
cf25d1e77d
Merge pull request #300 from anmonteiro/anmonteiro/covariant-promise
Make the type parameter for Promise.t covariant
2022-09-13 15:17:56 +01:00
Thomas Leonard
781e112c79 Add some extra documentaton for Fiber.List 2022-09-13 14:52:11 +01:00
Raphaël Proust
6e44a9f0b5 Move list functions into a dedicated submodule
Add deprecated versions of the original functions that were moved.
2022-09-13 14:43:36 +01:00
Antonio Nuno Monteiro
316f4f945c Re-add injectivity annotations 2022-09-12 13:32:44 -07:00
Thomas Leonard
8c83cb9318
Merge pull request #314 from talex5/fix-uring-message
linux_eio: fix kernel version number in log message
2022-09-12 15:29:58 +01:00
Thomas Leonard
7be66c045a linux_eio: fix kernel version number in log message
Reported-by: Nicolás Ojeda Bär
2022-09-09 10:57:15 +01:00
Bikal Lem
4f50e47650 buf_read: add skip_while1 2022-09-07 11:18:56 +01:00
Bikal Lem
ccd2eb04ac buf_read: add take_while1 2022-09-07 11:18:56 +01:00
Antonio Nuno Monteiro
90abf80879 Make the resolver contravariant on its type parameter 2022-09-05 18:43:54 -07:00
Anil Madhavapeddy
355478d6d9
Merge pull request #299 from talex5/docs
Add Best Practices section to README
2022-08-31 15:25:36 +01:00
Antonio Nuno Monteiro
024fe587bf fix formatting 2022-08-31 00:51:30 -07:00
Antonio Nuno Monteiro
a6f8a51e3f Make the type parameter for Promise.t covariant 2022-08-31 00:43:15 -07:00
Thomas Leonard
c24655c7d1 Add Best Practices section to README 2022-08-31 07:49:38 +01:00
Thomas Leonard
84e9ce9dfa
Merge pull request #295 from talex5/docs
Documentation improvements
2022-08-29 13:47:56 +01:00
Thomas Leonard
2f881405db Clarify that a mutex is not a rw-lock 2022-08-29 10:43:43 +01:00
Thomas Leonard
24936a95a7 Simplify concurrent cache example
The discussion of cancellation is distracting, and the using a switch
here makes the multi-domain case hard.
2022-08-29 10:41:55 +01:00
Thomas Leonard
0121984ad3 Clarify about Eio flows wrapping FDs 2022-08-29 10:39:09 +01:00
Thomas Leonard
8d21673032 Update installation instructions to alpha1
alpha0 still works, but there's no reason to use it now.
2022-08-29 10:26:40 +01:00
Thomas Leonard
8beda18c99
Merge pull request #294 from talex5/docker
Update Dockerfile for uring 0.4
2022-08-26 16:19:08 +01:00
Thomas Leonard
3a2e5c87b3 Update Dockerfile for uring 0.4 2022-08-26 16:15:17 +01:00
Thomas Leonard
d86a4fddc8
Merge pull request #293 from talex5/release
Minor documentation fixes
2022-08-26 15:49:10 +01:00
Thomas Leonard
935f053fc1 Minor documentation fixes 2022-08-26 15:48:00 +01:00
Thomas Leonard
32efe7a14e
Merge pull request #292 from talex5/release
Prepare release
2022-08-26 15:33:53 +01:00
Thomas Leonard
b3955da5cd
Merge pull request #291 from talex5/getaddr-typed
Add Eio.Net.getaddrinfo_{stream,datagram}
2022-08-26 15:25:09 +01:00
Thomas Leonard
6db0b7e6a5 Prepare release 2022-08-26 15:08:04 +01:00
Thomas Leonard
02a467240c Add Eio.Net.getaddrinfo_{stream,datagram}
These filter the results of `getaddrinfo` to ensure you get something of
the correct type.

This is used in the tests to avoid non-deterministic ordering of TCP vs
UDP results.
2022-08-26 14:06:18 +01:00
Thomas Leonard
42bdf96fa1
Merge pull request #290 from talex5/uring-0.4
Upgrade to uring 0.4
2022-08-26 13:44:29 +01:00
Thomas Leonard
1b16ca8c85 eio_linux: let uring track the number of active operations 2022-08-26 10:20:33 +01:00
Thomas Leonard
fca903a936 eio_linux: use uring for unlink and rmdir 2022-08-26 10:20:33 +01:00
Thomas Leonard
b31eceada4
Merge pull request #289 from talex5/linux-5-11
eio_linux: require Linux >= 5.11 for unlinkat
2022-08-25 13:22:11 +01:00
Thomas Leonard
0d4cad67fe eio_linux: require Linux >= 5.11 for unlinkat 2022-08-25 13:14:34 +01:00
Thomas Leonard
09845882a2
Merge pull request #288 from bikallem/getnameinfo
Eio.Net: add getnameinfo
2022-08-25 09:57:50 +01:00
Bikal Lem
146c4b4b63 Eio.Net: add getnameinfo 2022-08-24 11:19:05 +01:00
Thomas Leonard
7d34e9927c
Merge pull request #279 from patricoferris/fix-luv-poll-handles
Allow sharing of libuv poll handles
2022-08-23 09:29:30 +01:00
Thomas Leonard
8408dd4bb0 Don't check for cancellation in close
Calling [File.close] from a cancelled context would mark it as closed
but abort doing the actual close.
2022-08-22 12:04:47 +01:00
Thomas Leonard
7dd072aba7 Update poll mask when one queue becomes empty 2022-08-22 12:04:47 +01:00
Thomas Leonard
5c7d7dd752 Remove some Obj.magic 2022-08-22 12:04:47 +01:00
Patrick Ferris
47c8c3f218 Ignore handles that don't have a file number 2022-08-22 12:04:47 +01:00
Patrick Ferris
76d2d08c1e Fail waiting poll fibers on close 2022-08-22 12:04:47 +01:00
Thomas Leonard
ad527c04cc Simplify 2022-08-22 12:04:17 +01:00
Thomas Leonard
28510d5e73 Remove trailing white-space 2022-08-21 13:56:58 +01:00
Patrick Ferris
811d5aaf0c Fix luv implementation for simultaneous polling on same fd 2022-08-21 13:56:58 +01:00
Patrick Ferris
146e71cff5 Share callback across await functions 2022-08-21 13:56:58 +01:00
Patrick Ferris
89f37e5d19 Convert lf_queue to lwt_dllist 2022-08-21 13:56:58 +01:00
Patrick Ferris
d50f78dbcc Allow sharing of libuv poll handles 2022-08-21 13:56:55 +01:00
Thomas Leonard
1df4155ac3
Merge pull request #284 from talex5/fix-silent-exit
eio_luv: fix reporting of errors in async loop
2022-08-18 15:29:43 +01:00
Thomas Leonard
255f3d724f Remove trailing white-space 2022-08-18 15:14:36 +01:00
Thomas Leonard
37e9ff970a eio_luv: fix reporting of errors in async loop
If this raises an exception, luv just exits the process. This means that
MDX can't report the error.
2022-08-18 15:13:38 +01:00
Thomas Leonard
d694384fc9
Merge pull request #283 from talex5/buf-switch
Buf_write.create: make switch optional
2022-08-16 10:59:02 +01:00
Thomas Leonard
dfcc1ea50f Buf_write.create: make switch optional
The switch is useful to ensure that any fibers waiting for a flush are
cancelled on error. However, it's not essential, and whatever cancelled
the writer will probably cancel the flushing fibers too anyway. This
makes it easier to create buffers outside of the eio event loop and
is useful when porting existing code from Faraday.

Also, expose the `abort` function and the `Flush_aborted` exception,
so that callers can clean up manually.
2022-08-16 09:58:59 +01:00
Thomas Leonard
f06a379daf
Merge pull request #282 from talex5/rename-tests
Remote test_ prefix from test files
2022-08-16 09:27:25 +01:00
Thomas Leonard
870cad8d13 Remote test_ prefix from test files 2022-08-16 09:25:06 +01:00
Thomas Leonard
bf534ec665
Merge pull request #281 from talex5/docs
Mention Mutex, Semaphore and Condition in the README
2022-08-16 09:17:30 +01:00
Thomas Leonard
0127f6626d Mention Mutex, Semaphore and Condition in the README 2022-08-16 09:14:27 +01:00
Thomas Leonard
e4413913a8
Merge pull request #280 from talex5/clock-tests
Remove duplicate test
2022-08-15 09:42:36 +01:00
Thomas Leonard
430dbb18c8 Remove duplicate test
Since the API change in ed2382bed5e9c8f, there is no difference between
these two cases.
2022-08-15 09:39:04 +01:00
Thomas Leonard
db4156dabc
Merge pull request #278 from bikallem/getaddrinfo
Add support for getaddrinfo
2022-08-15 09:38:51 +01:00
Bikal Lem
9cc5e1ee6a Add Eio.Net.getaddrinfo 2022-08-15 09:26:11 +01:00
Thomas Leonard
5e7ad8ed18
Merge pull request #277 from talex5/condition
Add Eio.Condition
2022-08-11 15:34:31 +01:00
Thomas Leonard
49c22696d5 Eio.Condition: unlock mutex on cancellation
If we're cancelled while waiting for a condition, we should still
take the lock again before returning (as Lwt does).

Also, make the `mutex` required on `await` and have a separate
`await_no_mutex` for that case. This is to make it harder to forget the
mutex if you need it.
2022-08-11 11:11:54 +01:00
Lucas Pluvinage
b9c43ad7ae Add Eio.Condition 2022-08-10 22:29:19 +01:00
Thomas Leonard
f76139c68a
Merge pull request #276 from talex5/eio-debug
Allow overriding the traceln function
2022-08-09 10:17:46 +01:00
Thomas Leonard
c64eee75c0 Allow overriding the traceln function 2022-08-09 09:51:34 +01:00
Thomas Leonard
72c5cb21f5 Fix confusing label in eio_linux tests 2022-08-09 09:26:04 +01:00
Thomas Leonard
047c72bfb4
Merge pull request #274 from talex5/ocaml-version
Clarify OCaml version constraint
2022-08-09 09:25:21 +01:00
Thomas Leonard
50784bdd20
Merge pull request #273 from talex5/docs
Minor documentation fixes
2022-08-04 17:48:10 +01:00
Thomas Leonard
fa92df59a2 Clarify OCaml version constraint
`ocaml-base-compiler` is version `5.0.0~alpha1` but `ocaml` itself is
just `5.0.0`.
2022-08-04 14:31:17 +01:00
Thomas Leonard
e1c1abf37b Minor documentation fixes 2022-08-04 13:52:48 +01:00
Thomas Leonard
34d1d26ac4
Merge pull request #272 from talex5/fix-docker
Disable tests in Dockerfile
2022-08-04 12:21:21 +01:00
Thomas Leonard
46ec582c4e Disable tests in Dockerfile
Can't run tests as `docker bulild` doesn't provide an IPv6 loopback
address, so disable that for now.
2022-08-04 12:11:00 +01:00
Thomas Leonard
31a9d3dfa8
Merge pull request #271 from talex5/release
Prepare release
2022-08-03 14:03:38 +01:00
Thomas Leonard
8786e0ca64 Prepare release 2022-08-03 10:59:08 +01:00
Thomas Leonard
4498713dc4
Merge pull request #270 from talex5/path-doc
Improve documentation about paths
2022-08-03 09:48:51 +01:00
Thomas Leonard
fea436e699 Improve documentation about paths 2022-08-03 09:38:33 +01:00
Thomas Leonard
237841a77f
Merge pull request #269 from talex5/luv-fix-fairness
Fix scheduling fairness in luv backend
2022-08-03 09:15:03 +01:00
Thomas Leonard
1f97bea1bb Fix scheduling fairness in luv backend
The IO token was re-added as long as the run queue wasn't empty, but
once we ran out of things to do we dropped it and never added it back
again.

Now, we keep track of whether we have a token in the queue and, if not,
add one when running any non-IO job.
2022-08-02 13:56:43 +01:00
Thomas Leonard
dcf92cf7e1
Merge pull request #268 from talex5/luv-shutdown
Implement remaining shutdown commands for luv
2022-08-02 13:30:11 +01:00
Thomas Leonard
82e2228de4 Implement remaining shutdown commands for luv
Luv doesn't support them, but we can just get the FD and do it
manually.
2022-08-02 09:54:08 +01:00
Thomas Leonard
2aadabf424
Merge pull request #266 from talex5/dir-api
Change Dir.t to be a pair of a dirfd and a path
2022-07-29 13:36:19 +01:00
Thomas Leonard
5f91c6b5d5 Split Eio.Dir into Eio.{Path,Fs}
An `Eio.Dir.t` could also refer to a file, so the old name was
confusing.
2022-07-29 10:46:54 +01:00
Thomas Leonard
7f318b4cf1 Add label when pretty-printing dirs
e.g. the path `./foo` is now rendered as `<cwd:foo>` or `<fs:foo>`
depending on whether it's confined or not.

Also, fix `fs` in eio_luv. Relative paths should resolve relative to
`.`, not the root.
2022-07-29 10:03:50 +01:00
Thomas Leonard
ff5e2bf33e Change Dir.t to be a pair of a dirfd and a path
Instead of passing two arguments each time we want to refer to
something, we now just pass the tuple.

This avoids having to pass e.g. `cwd "."` - you can just use `cwd`.

`Dir.(/)` provides filename concatenation, which might make it easier to
support Windows in future.
2022-07-28 12:42:28 +01:00
Thomas Leonard
28d4ee1671
Merge pull request #265 from talex5/rename
Add Eio.Dir.rename
2022-07-28 10:37:20 +01:00
Thomas Leonard
9bfc954c19 Add Eio.Dir.rename
Also, simplify the handling of cwd/fs/dir-fd in eio_linux.
2022-07-27 15:39:20 +01:00
Thomas Leonard
57791d04a1
Merge pull request #264 from talex5/unlink
Add Eio.Dir.{unlink,rmdir}
2022-07-27 11:08:55 +01:00
Thomas Leonard
c73a1ebbd8 Add Eio.Dir.{unlink, rmdir} 2022-07-27 11:04:40 +01:00
Thomas Leonard
5b411f56cf
Merge pull request #263 from talex5/main-return
Allow returning a result from Eio_main.run
2022-07-26 13:58:48 +01:00
Thomas Leonard
a9620bcf3d Allow returning a result from Eio_main.run
This is useful for e.g. cmdliner. We want to parse the command line
arguments inside the event loop so that we can use Eio, but we want to
call `exit` with the exit code after the loop has cleaned up.
2022-07-26 09:35:31 +01:00
Thomas Leonard
2c1c7fa5c8
Merge pull request #262 from haesbaert/udpdomain
Fix IPv6 for UDP as well
2022-07-25 17:47:02 +01:00
Christiano Haesbaert
88cbddb4cf Fix IPv6 for UDP as well
Same as previous fixes for SOCK_STREAM.
2022-07-25 18:18:26 +02:00
Christiano Haesbaert
338d74bc8c
Merge pull request #261 from talex5/ipv6
eio-linux: fix IPv6 support
2022-07-25 13:31:04 +02:00
Thomas Leonard
68dcf4b7c7 eio-linux: fix IPv6 support
Use `PF_INET6` for IPv6 addresses, not `PF_INET`.
2022-07-25 11:40:48 +01:00
Thomas Leonard
7d758fc25b
Merge pull request #260 from talex5/socketpair
Add Eio_unix.socketpair
2022-07-25 11:00:38 +01:00
Thomas Leonard
089b176f23 Add Eio_unix.socketpair
Also, rename `take`/`peek` to `take_opt`/`peek_opt` to make way for
non-optional versions.
2022-07-25 10:28:47 +01:00
Thomas Leonard
74db28cf96
Merge pull request #259 from talex5/odoc
Minor odoc fixes
2022-07-25 10:18:24 +01:00
Thomas Leonard
640b84809e Minor odoc fixes 2022-07-25 10:17:15 +01:00
Thomas Leonard
85879987c0
Merge pull request #256 from SquidDev/feature/fiber-storage
Fiber-local storage
2022-07-25 10:15:30 +01:00
Thomas Leonard
c166b062f3 Move fiber-local storage tests to fiber test suite 2022-07-25 10:06:30 +01:00
Jonathan Coates
05d5bc8db9
Some further doc cleanup 2022-07-23 18:32:01 +01:00
Jonathan Coates
222136ca39
Clarify that FLS isn't always a good idea 2022-07-23 08:59:24 +01:00
Jonathan Coates
e79f75801a
Add without_binding and some docs 2022-07-23 08:55:39 +01:00
Thomas Leonard
bdc195a9e3
Merge pull request #257 from talex5/connection-refused
Use Eio.Net.Connection_reset in more places
2022-07-22 15:55:10 +01:00
Thomas Leonard
d405af5b1e Use Eio.Net.Connection_reset in more places
It's useful to be able to match on this as it's common to want to ignore
these errors.
2022-07-22 15:46:28 +01:00
Thomas Leonard
54d64d582e Testing some API changes 2022-07-21 09:13:05 +01:00
Jonathan Coates
afab28c088
Draft implementation of fiber-local storage 2022-07-20 10:35:31 +01:00
Thomas Leonard
c180013fb9
Merge pull request #255 from talex5/closed-fd
Report use of closed FDs better
2022-07-18 14:27:45 +01:00
Thomas Leonard
547622fdb7 Report use of closed FDs better
Previously, attempting to use a closed FD would raise an exception in
the scheduler context, causing the whole event loop to exit. Now it's
just reported back to the caller as an error. This means you get a stack
trace, a chance to clean up or recover, and you get to see some output
from MDX.
2022-07-18 14:23:20 +01:00
Thomas Leonard
b46ffba2c5
Merge pull request #254 from talex5/fix-cancel
Some fixes for cancellation
2022-07-18 11:15:23 +01:00
Thomas Leonard
efdbbbf1a6 Some fixes for cancellation
- Run cancel functions in the order in which the fibers were added, for
  simplicity.

- `Cancel.cancel` collects the cancel functions for all the fibers at
  once, and then calls them in series. This means that a job may have
  completed and been freed by the time its cancel function is called.
  Just ignore the cancellation in that case.

- If an eio_linux job succeeds, report the success even if it was also
  cancelled. This allows e.g. closing an allocated FD.
2022-07-18 11:09:01 +01:00
Thomas Leonard
48c14c9902
Merge pull request #252 from talex5/fork-daemon
Add Fiber.fork_daemon
2022-07-07 09:28:00 +01:00
Thomas Leonard
0b66a0bee5 Add Fiber.fork_daemon
This is useful to run a daemon/helper/service fiber that is intended to
keep running until the main fibers are finished and should then exit.

Uses include:

- Running an HTTP service exposing Prometheus metrics.
- Periodically collecting entropy.
- Monitoring a TCP connection and reconnecting if it fails.
2022-07-06 15:47:23 +01:00
Thomas Leonard
b3866d0512
Merge pull request #250 from talex5/iter
Add Fiber.iter
2022-07-06 10:57:52 +01:00
Thomas Leonard
1c668ba6bf Add Fiber.iter 2022-07-06 10:52:44 +01:00
Thomas Leonard
f6063251fe Factor out limiter logic in Fiber 2022-07-06 10:52:44 +01:00
Thomas Leonard
3ff58f57f0
Merge pull request #251 from talex5/unpin-odoc
Remove odoc-parser pin
2022-07-06 10:52:28 +01:00
Thomas Leonard
51f10c6b61 Remove odoc-parser pin
odoc-parser 1.0.1 has now been released with OCaml 5.0 support.
2022-07-06 09:50:47 +01:00
Thomas Leonard
dbc6cf1fb0
Merge pull request #248 from talex5/filter-map
Add Fiber.{filter, map, filter_map}
2022-07-05 11:18:19 +01:00
Thomas Leonard
1ce4590fac Add ?max_fibers to filter_map, etc, to limit concurrency 2022-07-05 11:14:30 +01:00
Thomas Leonard
bf15fc040b Add Fiber.{filter, map, filter_map}
These are concurrent versions of the corresponding operations in List.
2022-07-04 17:16:29 +01:00
Thomas Leonard
d0296624e6
Merge pull request #247 from talex5/integrations-doc
Document integrations
2022-07-04 16:26:30 +01:00
Thomas Leonard
8e482e1c91 Document integrations
Link to Async_eio and Eio_unix in the documentation.

Also, link to the API docs right at the top for quick access.
2022-07-04 13:29:52 +01:00
Thomas Leonard
7fe2afdd3e
Merge pull request #224 from talex5/5.0
Update to 5.0.0~alpha0
2022-07-04 10:15:53 +01:00
Thomas Leonard
ca75b75b99 Update Eio_mock link to point to the published odoc 2022-07-04 10:09:03 +01:00
Thomas Leonard
d86ccbd0b9 Use pin-depends for odoc-parser
Needed for CI, as it doesn't include the alpha repository.
2022-07-04 10:09:03 +01:00
Thomas Leonard
82cb4f7193 Update to 5.0.0~alpha0
Drop compatibility with 4.12+domains.

Add a Dockerfile for easy testing.
2022-07-04 10:09:03 +01:00
Thomas Leonard
daf46d7479
Merge pull request #246 from talex5/flush-on-error
Ensure Buf_write still flushes if an exception is raised
2022-07-01 11:11:14 +01:00
Thomas Leonard
ca27cd315d Ensure Buf_write still flushes if an exception is raised 2022-07-01 10:38:40 +01:00
Thomas Leonard
ae7675e911
Merge pull request #245 from talex5/double-close
Do not allow close on accept_fork socket
2022-07-01 09:15:32 +01:00
Thomas Leonard
946fce7b39 Do not allow close on accept_fork socket
`accept_fork` closes the socket when the function returns. Prevent the
function from trying to close the socket itself.

Fixes #244.
2022-06-29 15:02:04 +01:00
Thomas Leonard
f7491d0f6a
Merge pull request #243 from talex5/release
Prepare release
2022-06-28 16:51:02 +01:00
Thomas Leonard
0b56cec54f Prepare release 2022-06-28 16:31:50 +01:00
Thomas Leonard
a418022ab2
Merge pull request #242 from talex5/buf_write_switch
Buf_write.of_buffer needs a switch too
2022-06-28 14:15:00 +01:00
Thomas Leonard
8c581de14b Buf_write.of_buffer needs a switch too 2022-06-28 14:01:12 +01:00
Thomas Leonard
9387aa9074
Merge pull request #241 from talex5/buf_write_switch
Ensure Buf_write doesn't leak fibers
2022-06-28 10:11:52 +01:00
Thomas Leonard
9188b31cde Ensure Buf_write doesn't leak fibers
A writer could be released while a fiber was waiting for a flush to
complete. Now `Buf_write.create` takes a switch and aborts any waiting
fibers when the switch finishes.
2022-06-28 10:09:06 +01:00
Thomas Leonard
bdfb3faa64
Merge pull request #240 from talex5/accept-and-fork
Replace accept_sub with accept_fork
2022-06-28 09:42:38 +01:00
Thomas Leonard
36e39cc2e7 Replace accept_sub with accept_fork
Now that cancellation is separate to switches, there's no need for a
`sw` argument here. This also removes the need for the (complicated!)
`Fiber.fork_on_accept`. When accepting, we now attach the new socket to
the parent switch, and just make sure to close it when the handler
returns manually.
2022-06-28 09:14:02 +01:00
Thomas Leonard
03cbe41a03
Merge pull request #235 from talex5/buf_write
Add Eio.Buf_write
2022-06-27 14:14:16 +01:00
Thomas Leonard
73ec23c3f9 Add Buf_write.with_flow
`with_flow` wraps an Eio sink, flushing to it automatically when the writing fiber blocks.
This is intended to be then normal way of using `Buf_write` from Eio.

Some other changes to Faraday:

- I replaced `yield` with `pause` and `unpause`, as it was unclear to me what it should do.
- `flush` now waits for the flush to complete instead of taking a callback (since we now have effects).
- `operation` is now `await_batch`. It now waits until data is available to write instead of returning `Yield`.
- `shift_buffers` and `shift_flushes` are now tail-recursive.
- Fixed overflow bug in `shift_flushes`.
- Removed `write_` prefix from write functions, as it's now in the module name.
- `string` and `bytes` now check the range is valid.
- `serialize` no longer hangs if it gets receives `Closed.
2022-06-27 14:08:23 +01:00
Thomas Leonard
930325258c Replace iovec with Cstruct.t 2022-06-27 10:25:49 +01:00
Thomas Leonard
61dd5d2a73 Import of Faraday 0.7.2 as Buf_write 2022-06-27 10:25:49 +01:00
Thomas Leonard
62e2964fc9
Merge pull request #239 from talex5/writev-single
Allow short writes in Read_source_buffer
2022-06-27 10:25:34 +01:00
Thomas Leonard
2f54e3a198 Allow short writes in Read_source_buffer
When performing buffered writes and the OS doesn't write all the data in
one go, it probably means it isn't ready for more. We can let the
application produce more data while we're waiting and then do the next
write with more buffers, instead of trying to finish the original
shorter write first.

This changes `Read_source_buffer` to allow returning the number of bytes
written. It also updates the mock output to show the individual buffers
being written rather than the combined string.

Also, add `Linux_eio.Low_level.writev_single` to expose this behaviour
directly.
2022-06-27 10:20:57 +01:00
Thomas Leonard
080d2c31e4
Merge pull request #238 from talex5/deadlock
Eio_mock.Backend: detect deadlock
2022-06-27 08:50:53 +01:00
Thomas Leonard
aa2d046985 Eio_mock.Backend: detect deadlock
Deadlock detection doesn't work for most backends because they are
always waiting for a signal from another domain, but the mock backend
only supports a single domain anyway and it's useful to detect deadlocks
in tests.
2022-06-26 20:57:37 +01:00
Thomas Leonard
e368799322
Merge pull request #237 from talex5/mock-backend
Move Eio_null to Eio_mock.Backend
2022-06-26 11:34:55 +01:00
Thomas Leonard
c4d19d5495 Move Eio_null to Eio_mock.Backend
It can be useful for tests to be able to run an event loop without
having to depend on eio_main.

Also, replace the inefficient list-based queue with a proper Lf_queue.
2022-06-25 22:14:37 +01:00
Thomas Leonard
475c03ed26
Merge pull request #236 from talex5/core-mli
Remove some internal types from Eio__core.mli
2022-06-25 12:46:45 +01:00
Thomas Leonard
62cbd205f4 Remove some internal types from Eio__core.mli 2022-06-25 09:38:31 +01:00
Thomas Leonard
6360782486
Merge pull request #234 from talex5/split-core
Move interfaces to individual files
2022-06-24 13:10:47 +01:00
Thomas Leonard
0827a9baf0 Split out some interfaces from eio.mli
This makes the code more modular, and allows finding unused symbols.

Note: `Eio.Unix_perm` is now `Eio.Dir.Unix_perm`.
2022-06-24 12:12:02 +01:00
Thomas Leonard
80f2f5c218 Split core modules to eio_core
This makes it more obvious that these are independent of the OS
abstractions, ensures that the OS abstractions only use exposed APIs,
and allows moving other interfaces into separate files.
2022-06-24 12:01:02 +01:00
Thomas Leonard
41632f1f9e
Merge pull request #232 from talex5/inline-consume
Inline Buf_read.consume
2022-06-23 14:17:16 +01:00
Thomas Leonard
a359f29265 Inline Buf_read.consume
+Read 100000000 bytes in 0.423s       Before
    +Read 100000000 bytes in 0.334s       After

Also, remove some misplaced and unnecessary attributes.
2022-06-23 09:25:05 +01:00
Thomas Leonard
8e9974741d
Merge pull request #231 from talex5/5.0-compat
Minor updates for 5.0.0~alpha0
2022-06-21 16:07:14 +01:00
Thomas Leonard
1b511e81ef Minor updates for 5.0.0~alpha0 2022-06-21 16:00:06 +01:00
Thomas Leonard
352bea331c
Merge pull request #230 from talex5/bench_buf_read
Add Buf_read benchmark and optimise a bit
2022-06-21 15:05:05 +01:00
Thomas Leonard
3aef5a0c26 Split out slow path of Buf_read.ensure
Helps the benchmark a bit:

    +Read 100000000 bytes in 0.558s	Before
    +Read 100000000 bytes in 0.402s 	After
2022-06-21 12:40:05 +01:00
Thomas Leonard
5857d0193d Add buf_read benchmark 2022-06-21 12:40:05 +01:00
Thomas Leonard
ababdc5f0a
Merge pull request #228 from talex5/mocks
Add eio.mock library for testing
2022-06-20 15:46:43 +01:00
Thomas Leonard
a23fa39835 Add eio.mock library for testing
At the moment it has mock flows and networks.

Also, add missing `close` and `probe` methods to some network types.
2022-06-20 15:30:32 +01:00
Thomas Leonard
8e2fa38274
Merge pull request #227 from talex5/pair
Add `<*>` combinator to Buf_read
2022-06-20 10:19:40 +01:00
Thomas Leonard
5c8dfec744 Add <*> combinator to Buf_read
We already have `<*` and `*>`.
2022-06-17 17:47:34 +01:00
Thomas Leonard
5a9c3cb4b4
Merge pull request #226 from talex5/traceln-unhandled
Fallback for traceln without an effect handler
2022-06-17 14:54:38 +01:00
Thomas Leonard
35011837e9 Fallback for traceln without an effect handler
`traceln` tries to get the default handler from the event loop.
But if run outside of an event loop it would crash with `Unhandled`.
Instead, fall back to writing to stderr.
2022-06-17 14:36:44 +01:00
Thomas Leonard
f83a1e5dfd
Merge pull request #225 from talex5/of-buffer
Add Buf_read.{of_buffer, of_string, parse_string{,_exn}, return}
2022-06-16 11:54:19 +01:00
Thomas Leonard
72770299e0 Add Buf_read.{of_buffer, of_string, parse_string{,_exn}, return}
`Buf_read.of_buffer` is slightly more efficient than reading from a
flow (in the case where you have all the data at the start), as it
avoids allocating a fresh buffer and copying the data.

The others are simple convenience functions.
2022-06-16 11:21:08 +01:00
Thomas Leonard
c31f44081a
Merge pull request #223 from talex5/mutex
Add Eio.Mutex
2022-06-15 09:17:06 +01:00
Thomas Leonard
5019693c96 Use Mutex.use_{ro,rw} to simplify API
For rw access we now always poison, and offer to protect from
cancellation. For ro access we just unlock.

Also, added some comments to argue about the correctness.
2022-06-14 12:19:38 +01:00
Thomas Leonard
4f5c10440d Some improvements to Eio.Mutex
- Add benchmarks for the mutex.
- Make `waiters` be a field. This simplifies the code by removing one
  state, and doesn't seem to affect performance.
- Make the API and documentation closer to Stdlib.Mutex.
- Replace `is_locked` with `try_lock`, since the result of `is_locked` isn't
  generally useful as it might already have changed.
- Add poisoning to `with_lock`.
2022-06-13 17:24:06 +01:00
Lucas Pluvinage
67f0f96810 Add Eio.Mutex 2022-06-13 17:14:35 +01:00
Thomas Leonard
3ce0eccceb
Merge pull request #222 from talex5/fork-when-cancelled
Check for cancellation when creating a non-protected child context
2022-06-10 11:44:50 +01:00
Thomas Leonard
582e3d7fb7 Check for cancellation when creating a non-protected child context
Otherwise we can end up with a cancelled parent context with a
non-cancelled and non-protected child.
2022-06-10 11:09:40 +01:00
Thomas Leonard
026a371f43
Merge pull request #220 from talex5/lwt-eio
Lwt_eio has moved
2022-06-03 14:54:25 +01:00
Thomas Leonard
5ca895c635 Lwt_eio has moved 2022-06-03 14:37:59 +01:00
Thomas Leonard
f6b97a2e2a
Merge pull request #219 from talex5/faster-read-dir
eio_linux: make read_dir faster
2022-06-01 13:38:11 +01:00
Thomas Leonard
df430b4b05 eio_linux: make read_dir faster
This improves performance of directory reads by using a larger buffer
and by performing all reads in a single systhread, rather than creating
a new one for each chunk.

- `Read 152448 items in 1.188156 seconds` with the old code.
- `Read 152448 items in 0.217013 seconds` with the new code.

It also ensures we can already read at least one item.
2022-06-01 13:06:52 +01:00
Thomas Leonard
4468f195de
Merge pull request #190 from talex5/cmdliner-1.1
Update to cmdliner.1.1.0
2022-06-01 11:29:37 +01:00
Thomas Leonard
52e744f793 Update to cmdliner.1.1.0 2022-06-01 10:57:15 +01:00
Thomas Leonard
54812335f4
Merge pull request #218 from talex5/readdir
Eio_linux: fix read_dir
2022-06-01 10:52:33 +01:00
Thomas Leonard
85a549f849 Improve performance of read_dir
Read 152441 items in 34.181577 seconds with the old code.
Read 152441 items in 1.181868 seconds with the new code.
2022-06-01 10:04:18 +01:00
Thomas Leonard
c1e84733d2 Eio_linux: fix read_dir
It was ignoring the directory and always reading from env#fs, but with
`Resolve.beneath`.
2022-06-01 10:01:17 +01:00
Thomas Leonard
3bcf6f3fbd
Merge pull request #207 from patricoferris/readdir
Add readdir feature
2022-06-01 09:18:18 +01:00
Patrick Ferris
aee2e0620a Use dirent64 and fix C list building 2022-05-27 22:20:23 +01:00
Patrick Ferris
80d04d2ce0 Move unblock to eio_unix 2022-05-27 22:20:23 +01:00
Patrick Ferris
f4e0e54fcf Use getdents64 for linux read_dir 2022-05-27 22:20:23 +01:00
Patrick Ferris
d7b5465ed6 Make read_dir tests consistent with others 2022-05-27 22:19:32 +01:00
Patrick Ferris
4a9bde55f6 Use suspend effect and sort entries for readdir 2022-05-27 22:19:32 +01:00
Patrick Ferris
f8ee699f74 Async readdir for uring with systhreads 2022-05-27 22:19:32 +01:00
Patrick Ferris
c64fc76135 Add libuv readdir feature 2022-05-27 22:19:32 +01:00
Thomas Leonard
751e2b4e68
Merge pull request #217 from talex5/readme-5.0
Improve the installation instructions
2022-05-22 11:58:59 +01:00
Thomas Leonard
55c08837ab Improve the installation instructions
- Say how to install 5.0+trunk, and mention that this is needed for arm.
- Show how to install a stable release from opam-repository.
2022-05-22 11:40:08 +01:00
Thomas Leonard
29ff919162
Merge pull request #213 from TheLortex/time-scheduling
Eio_linux: handle sleeping tasks that are now due before paused fibers
2022-05-19 09:47:00 +01:00
Lucas Pluvinage
1d2e80dc1f IO vs yielding: similar change for the luv backend 2022-05-18 21:28:25 +01:00
Lucas Pluvinage
0612c3ddce Allow IO to happen even if a fiber keeps yielding 2022-05-18 21:28:25 +01:00
Thomas Leonard
42d90547df
Merge pull request #215 from patricoferris/patch-1
Update README.md
2022-05-13 11:54:31 +01:00
Patrick Ferris
b478cb9e99
Update README.md 2022-05-09 13:25:53 +01:00
Bikal Lem
cfad5e826d
Merge pull request #212 from bikallem/eintr
Handle EINTR error when calling `getrandom` system call
2022-05-03 11:40:23 +01:00
Bikal Lem
6f6b2eee4f eio_linux: use do/while and ret == -1 2022-04-29 10:45:58 +01:00
Bikal Lem
5a25ee98bc lib_eio_luv: handle EINTR when calling getrandom 2022-04-22 16:32:59 +01:00
Bikal Lem
26488603ef eio_linux: handle EINTR when calling getrandom 2022-04-22 16:27:37 +01:00
344 changed files with 35828 additions and 8045 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
Dockerfile
_build
.git
**/*.swp

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
# To work around MDX issues
README.md text eol=lf
CHANGES.md whitespace=-blank-at-eol

66
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,66 @@
name: Main workflow
on:
pull_request:
push:
jobs:
build:
strategy:
fail-fast: false
matrix:
os:
- macos-latest
ocaml-compiler:
- 5.2.x
local-packages:
- eio eio_posix eio_main
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Use OCaml ${{ matrix.ocaml-compiler }}
uses: ocaml/setup-ocaml@v2
with:
ocaml-compiler: ${{ matrix.ocaml-compiler }}
opam-local-packages:
opam-disable-sandboxing: true
- run: opam --cli=2.1 pin -yn --with-version=dev .
- run: opam install ${{ matrix.local-packages }} --deps-only --with-test
- run: opam install ${{ matrix.local-packages }} --with-test
windows:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set-up OCaml
uses: ocaml/setup-ocaml@v2
with:
opam-pin: false
opam-depext: false
ocaml-compiler: ocaml.5.2.0,ocaml-option-mingw
opam-repositories: |
dra27: https://github.com/dra27/opam-repository.git#windows-5.0
normal: https://github.com/ocaml/opam-repository.git
# --with-version=dev is not available, and --with-test also tries running tests for packages (like MDX) which fail...
- run: |
opam pin -yn eio.dev .
opam pin -yn eio_windows.dev .
opam pin -yn eio_main.dev .
opam install eio eio_windows eio_main --deps-only --with-test
- run: opam exec -- dune build
- run: opam exec -- dune runtest
- run: opam exec -- dune exec -- ./examples/net/main.exe
- run: opam exec -- dune exec -- ./examples/fs/main.exe
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build the Docker image
run: docker build .

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
_build _build
_opam
.ocamlformat
.*.swp .*.swp
*.install *.install

View File

@ -1,3 +1,902 @@
## v1.2
Changes:
- Make `fork_action.h` a public header (@patricoferris #769, reviewed by @talex5).
Allows other libraries to add new actions.
- Record trace event when spawning processes (@talex5 #749).
Spawning a subprocess can take a long time in some cases, so show it clearly in the traces.
- Eio_unix.Net: make some return types more polymorphic (@talex5 #744).
Bug fixes:
- Preserve backtraces in `fork_daemon` (@talex5 #779).
- Eio.Path: always use "/" as separator (@talex5 #743).
Linux backend:
- Allow `alloc_fixed_or_wait` to be cancelled (@talex5 #753).
- Avoid triggering a (harmless) TSan warning (@talex5 #754, reported by @avsm).
Windows backend:
- Unregister FDs on cancel (@talex5 #756).
Fixes `exception Unix.Unix_error(Unix.ENOTSOCK, "select", "")`.
- Work around problems in `Unix.getaddrinfo` (@talex5 #780).
Fixes e.g. `No addresses found for host name "127.0.0.1"`.
- Group `ECONNABORTED` with other connection reset errors (@talex5 #783).
- Check `has_symlink` for tests (@create2000 #771, reviewed by @patricoferris and @talex5).
- Improve `openat` error handling (@talex5 #742, reported by @kentookura).
Fixes `exception Unix.Unix_error(Unix.ENOENT, "openat", "")`.
Documentation:
- examples/fs: show how to read files while scanning (@talex5 #745).
- Add example to `Buf_read.seq` documentation (@talex5 #739, requested by @darrenldl and @rizo).
Build and test:
- Fix tests on OpenBSD (@talex5 #782).
- Add advice about using AI for code generation (@patricoferris #765, reviewed by @avsm and @talex5).
- Minor code cleanups (@talex5 #755).
- Define `struct clone_args` for linux-lts versions that don't have it (@copy #741, reviewed by @talex5).
- eio_linux: refactor fixed buffer code (@talex5 #752).
## v1.1
New features:
- Add `Eio.Path.symlink` (@patricoferris #715, reviewed by @talex5).
- Add `Eio.Pool.use ~never_block` (@SGrondin #657, reviewed by @talex5).
- Add `Eio_unix.Net.import_socket_listening` (@alyssais #733).
- Add `Eio.Time.Timeout.sleep` (@talex5 #726).
Documentation:
- Add `examples/fs` showing how to walk a directory tree (@talex5 #730).
- README: explain that `read_all` reads until shutdown (@talex5 #717, reported by @Wenke-D).
- Use long dash in README title (@lucperkins #718).
Linux backend:
- Require Linux >= 5.15 (@talex5 #720, reviewed by @SGrondin and @avsm).
Removes a work-around that required checking whether every flow was a tty.
- Don't call submit immediately before wait (@talex5 #728).
This is slightly faster and makes the traces clearer.
- Don't record submit events when there's nothing to submit (@talex5 #729).
Makes the traces a bit clearer.
- Split flow into its own file (@talex5 #727).
- Add work-around for signals race (@talex5 #734).
POSIX backend:
- Add `_BSD_SOURCE` flag to fix build on OpenBSD (@prgbln #722).
- Fix sandboxed path resolution on OpenBSD (@jebrosen #723, reviewed by @talex5).
OpenBSD uses `ELOOP` when opening a symlink with `O_NOFOLLOW`.
Build and test:
- Benchmarks: record uname, Eio backend, and number of cores (@talex5 #719).
- Update to MDX 2.4.1 for OCaml 5.2 (@talex5 #712).
## v1.0
New features:
- Add `Eio_unix.Cap` module to enable Capsicum mode (@talex5 #697, reviewed by @SGrondin).
- eio_linux: expose more functions in the `Low_level` module (@talex5 #705, reviewed by @SGrondin).
Add all the functions used by other parts of eio_linux (`openat`, `mkdir`, `read_link`, `unlink`, `rename` and `pipe`).
Tidied the API up a bit too:
- `mkdir_beneath` is now just `mkdir`.
- `statx_confined` is now just `statx`.
- `open_dir` is gone; the single user now calls `openat` directly.
Documentation:
- Add README documentation for `Eio.Executor_pool` (@SGrondin @talex5 #707, reviewed by @Sudha247).
- eio_linux: remove logging (@talex5 #708, requested by @clecat).
There were only two remaining uses of Logs, neither of which has proved useful.
Build:
- Add upper-bound on MDX (@talex5 #706).
The new version attempts to execute included blocks.
- Fix tests to pass with both old and new Kcas (@polytypic #704).
- Make posix `open_beneath` test idempotent (@SGrondin #703).
- Executor_pool: mention requested weight in error message (@talex5 #702, reported by @yawaramin).
## v0.15
New features:
- eio_posix: use directory FDs instead of realpath (@talex5 #694 #696, reviewed by @SGrondin).
Using realpath was an old hack from the libuv days and subject to races. It was also slow.
- Keep pool of systhreads for blocking operations (@SGrondin @talex5 #681).
This is much faster than creating a new thread for each operation.
It mainly benefits the eio_posix backend, as that uses lots of systhreads.
- Make `Switch.on_release` thread-safe (@talex5 #684, requested by @art-w and @clecat).
This allows resource pools to be shared between domains easily.
- Add `Eio.Path.read_link` (@talex5 #686).
- Add `Eio_unix.Fd.is_open` (@talex5 #690).
- Include backtrace in systhread errors (@talex5 #688, reviewed by @SGrondin).
Also, add `Eio.Exn.empty_backtrace` as a convenience.
- eio.mock: add tracing support to mock backend (@talex5 #687).
- Improve tracing (@talex5 #675 #683 #676, reviewed by @SGrondin).
Update tracing section of README and trace more things
(`run_in_systhread`, `close`, `submit`, `traceln`, cancellation and domain spawning).
Documentation:
- Link to verification work in docs (@talex5 #682).
- Add more trace diagrams to README (@talex5 #698).
- Adjust COC contacts (@polytypic #685, reviewed by @Sudha247).
Bug fixes:
- eio_linux: retry `openat2` on `EAGAIN` (@talex5 #693, reviewed by @SGrondin).
- eio_posix and eio_windows: check for IO periodically (@talex5 #674).
- Handle EPERM when trying to initialise uring (@talex5 #691).
This can happen when using a Docker container.
Build and tests:
- Benchmark `Eio_unix.run_in_systhread` (@talex5 #678, reviewed by @SGrondin).
- Enable lintcstubs for `Eio_unix.Private` too (@talex5 #689).
- Stat benchmark: report cleanup time and optimise (@talex5 #692).
- Make benchmarks start faster (@talex5 #673).
- Update build for new eio-trace CLI (@talex5 #699).
- Expect opam-repo-ci tests to fail on macos (@talex5 #672).
## v0.14
New features / API changes:
- Add `Eio.Executor_pool` (@SGrondin #639, reviewed by @talex5).
Provides an easy way to distribute jobs across domains.
- Add `Fiber.first ~combine` and `Fiber.n_any` (@SGrondin @talex5 #587).
Allows keeping both results in the case where multiple fibers succeed.
- Add `Eio_mock.Backend.run_full` with auto-advancing mock clock (@talex5 #644, reviewed by @SGrondin).
Simplifies testing of code using clocks.
- Add `Buf_write.printf` (@SGrondin @talex5 #655).
- Add `Net.listening_addr` (@mefyl #555, reviewed by @patricoferris @talex5).
Useful to get the socket's address if the OS assigns it.
- Add `Promise.try_resolve` (@talex5 #646).
- Remove `Cancel_hook_failed` exception (@talex5 #640).
Didn't seem to be used and broke dscheck.
Tracing:
- Improve tracing (@TheLortex @patricoferris @talex5 #656).
Trace cancellation contexts and OS operations, and simplify API.
- Add labels to switches (@talex5 #661, reviewed by @SGrondin).
- `Fiber.all`: use the parent fiber (@talex5 #665, reviewed by @SGrondin).
Cleans up the traces a bit.
Performance:
- Faster and simpler `Lf_queue` (@talex5 #647, based on work by @polytypic).
- Optimise `Flow.copy` with `Buf_read.as_flow` (@talex5 #663, reviewed by @SGrondin, reported by @leostera).
Bug fixes:
- Fix handling of very long IO vectors (@talex5 #653, reported by @Cjen1).
- eio_posix: use `caml_enter_blocking_section` in more places (@talex5 #654, reviewed by @SGrondin).
- eio_posix: work around `caml_unix_alloc_sockaddr` bug (@talex5 #651).
- Remove default backtrace from `Switch.fail` (@talex5 #664).
Documentation:
- Organise eio.mli better (@talex5 #667).
- Fix quoting of quotes in process error messages (@talex5 #666, reviewed by @SGrondin).
- Mention Path module in File and Fs documentation (@talex5 #659, requested by @clecat).
- Minor documentation updates (@SGrondin @talex5 #670).
Build / internals:
- Allow closing synchronous streams (@talex5 #641, reviewed by @SGrondin).
This isn't currently exposed in the public interface.
- Fix non-idempotent tests (@SGrondin #662).
- eio_windows: add explicit fmt dependency (@talex5 #643).
## v0.13
New features / API changes:
- Add `Flow.read_all` (@SGrondin #596, reviewed by @talex5 @rbjorklin).
- Add `Path.stat` (@patricoferris @talex5 @avsm #617 #618 #624 #620, reviewed by @SGrondin).
- Add `Path.rmtree` (@talex5 #627 #628, reviewed by @SGrondin).
- Add `Path.mkdirs` and `Path.split` (@patricoferris @talex5 #625).
- Add `Eio.File.{seek,sync,truncate}` (@talex5 #626).
- Add `Eio.Path.{kind,is_file,is_directory}` (@patricoferris @talex5 #623, reviewed by @avsm).
- Switch from CTF to OCaml 5.1 runtime events (@TheLortex @patricoferris @talex5 #634 #635, reviewed by @avsm).
This is a minimal initial version.
Documentation:
- Document `File.Stat` record fields (@avsm @talex5 #621).
- Update README section about `env` (@talex5 #614, reported by @jonsterling).
Build and test changes:
- Add `File.stat` benchmark (@talex5 #616).
- Add `Path.stat` benchmark (@patricoferris @talex5 #630).
- eio_linux: mark as only available on Linux (@talex5 #629).
- Make MDX tests idempotent (@SGrondin #601, reviewed by @talex5).
- Allow trailing whitespace in CHANGES.md (@talex5 #632).
- Update minimum OCaml version to 5.1 (@talex5 #631).
- Generate prototypes for C stubs from ml files (@talex5 #615).
- Don't try to compile uring support on centos 7 (@talex5 #638, reported by @zenfey).
## v0.12
New features / API changes:
- Replace objects with variants (@talex5 @patricoferris #553 #605 #608, reviewed by @avsm).
Some potential users found object types confusing, so we now use an alternative scheme for OS resources.
For users of the resources, the only thing that changes is the types:
- Instead of taking an argument of type `#foo`, you should now take `_ foo`.
- Instead of returning a value of type `foo`, you should now return `foo_ty Eio.Resource.t`.
To provide your own implementation of an interface, you now provide a module rather than an object.
For example, to provide your own source flow, use `Eio.Flow.Pi.source (module My_source)`.
If you want to define your own interfaces, see the `Eio.Resource` module documentation.
- Add `Eio.Pool` (@talex5 @darrenldl #602, reviewed by @patricoferris).
A lock-free pool of resources. This is similar to `Lwt_pool`.
- Add `Eio.Lazy` (@talex5 #609, reviewed by @SGrondin).
If one fiber tries to force a lazy value while another is already doing it,
this will wait for the first one to finish rather than raising an exception (as `Stdlib.Lazy` does).
- Add `Eio.Path.native` (@talex5 #603, reviewed by @patricoferris).
This is useful when interacting with non-Eio libraries, for spawning sub-processes, and for displaying paths to users.
- Add `Flow.single_write` (@talex5 #598).
- Add `Eio.Flow.Pi.simple_copy` (@talex5 #611).
Provides an easy way to implement the `copy` operation when making your own sink.
- Eio_unix: add FD passing (@talex5 #522).
Allows opening a file and passing the handle over a Unix-domain socket.
- Add `Process.run ?is_success` to control definition of success (@SGrondin #586, reviewed by @talex5).
- Add `Eio_mock.Domain_manager` (@talex5 #610).
This mock domain manager runs everything in a single domain, allowing tests to remain deterministic.
- Add `Eio.Debug.with_trace_prefix` (@talex5 #610).
Allows prefixing all `traceln` output. The mock domain manager uses this to indicate which fake domain is running.
Bug fixes:
- Fork actions must not allocate (@talex5 #593).
When using multiple domains, child processes could get stuck if they forked while another domain held the malloc lock.
- eio_posix: ignore some errors writing to the wake-up pipe (@talex5 #600).
If the pipe is full or closed, the wake-up should simply be ignored.
Build/test fixes:
- Fix some MDX problems on Windows (@polytypic #597).
- The README depends on kcas (@talex5 #606).
- Clarify configuration for lib_eio_linux and enable tests on other arches (@dra27 #592).
- eio_linux tests: skip fixed buffer test if not available (@talex5 #604).
- eio_windows: update available line to win32 (@talex5 #588 #591).
## v0.11
New features / API changes:
- Extend `Eio.Condition` API (@talex5 #563).
- `loop_no_mutex` is a simpler and more efficient way to way for a condition.
- `register_immediate` allows integration with other IO libraries.
- Expose `Eio.Stdenv.backend_id` (@bord-o #560, reviewed by @talex5).
Useful in tests to report which backend is being used.
- Remove deprecated features (@talex5 #552, reviewed by @avsm).
These were all already marked as deprecated in v0.10 and are now gone completely:
- `Fiber.fork_sub`
- `Eio_unix.{FD,Ipaddr,socketpair,getnameinfo}`
- `Eio_linux.{FD,get_fd,get_fd_opt}`
- `Eio_posix.Low_level.Fd`
- Allow calling `close` more than once (@talex5 #547, requested by @anmonteiro, reviewed by @patricoferris, @avsm).
- Add `close` to socket type (@talex5 #549).
Simplifies the type signatures a bit by avoiding having to mention this everywhere.
Bug fixes:
- Fix handling of empty path strings (@talex5 #569, reported by @SGrondin).
Using "" instead of "." in some places resulted in an error.
- eio_posix: fix update to watched FDs on cancel (@talex5 #574, reported and reviewed by @quernd).
Cancelling the last watcher of an FD didn't remove it from the set passed to `poll`,
which could result in constant wake-ups.
- eio_posix: fix `pread` at end-of-file (@talex5 #581, reported by @SGrondin).
It tried to return 0 instead of `End_of_file`, triggering an assertion.
- eio_posix: don't reap non-Eio child processes (@talex5 #562).
This allows spawning processes with e.g. the stdlib or Lwt
(but see https://github.com/ocaml-multicore/lwt_eio/pull/19 for Lwt support).
- Preserve backtraces across `Domain_manager.run` (@talex5 #571).
See https://github.com/ocaml/ocaml/issues/12362.
- Correct the backend selection for Cygwin (@dra27 #557).
Use `eio_posix`, not `eio_windows` in this case.
Other changes:
- Simplify dune files with dune 3.9's `build_if` (@talex5 #582).
- Remove `Waiters` from `Eio_core` (@talex5 #567).
`Eio.Switch` no longer uses this so it can finally be removed.
- Use `Fmt.Dump.signal` to format signals (@talex5, @MisterDA #543).
Documentation:
- Add some notes about thread-safety in the documentation (@talex5 #568).
## v0.10
New features / API changes:
- Add `Eio.Process` for cross-platform subprocess support (@patricoferris @talex5 #499, reviewed by @anmonteiro @avsm @haesbaert).
- Add `Eio_unix.Net module` (@talex5 #516, reviewed by @avsm).
The Unix network APIs have been cleaned up and moved here, and some missing datagram operations have been added.
`send` now takes an iovec, not just a single buffer.
- Add support for domain local await (@polytypic @talex5 #494 #503).
Allows sharing e.g. kcas data-structures across Eio and Domainslib domains.
- Add initial eio_windows backend (@patricoferris @talex5 #497 #530 #511 #523 #509, reviewed by @avsm @polytypic).
- Remove eio_luv backend (@talex5 #485).
It was only used on Windows, and has been replaced by eio_windows.
- Unify `Eio_linux.FD` and `Eio_posix.Fd` as `Eio_unix.Fd` (@talex5 #491).
Now that eio_luv is gone, there is no need for different backends to have different types for wrapped file descriptors.
- Move `Eio.Stdenv.t` to `Eio_unix.Stdenv.base` (@talex5 #498).
Note that the rest of `Eio.Stdenv` is still there; only the definition of a full Unix-like environment has moved.
- Deprecation cleanups (@talex5 #508).
Removed some APIs that were already marked as deprecated in Eio 0.8.
Bug fixes:
- eio_linux: fall back to `fork` if `clone3` is unavailable (@talex5 #524, reported by @smondet, reviewed by @avsm).
Docker's default security policy blocks `clone3`.
- Don't call `accept_fork`'s error handler on cancellation (@talex5 #520).
This isn't an error and should not be reported.
- Fix `eio_unix_is_blocking` C stub (@patricoferris #505, reviewed by @talex5).
- Fix `Condition.await bug` when cancelling (@polytypic @talex5 #487).
- Buf_write: fix flush returning too early (@talex5 #539, reported by @cometkim).
- Ignore `ENOTCONN` errors on socket shutdown (@avsm #533, reported by @patricoferris, reviewed by @talex5).
Documentation:
- Link to developer meetings information (@talex5 @Sudha247 #515).
- Adopt OCaml Code of Conduct (@Sudha247 #501).
- Add README links to Meio and Lambda Capabilities blog post (@talex5 #496).
- Document mirage `Ipaddr` conversion (@RyanGibb @patricoferris @talex5 #492).
- Document how to use Domainslib from Eio (@talex5 #489, reviewed by @polytypic @patricoferris).
Other changes:
- Run benchmarks with current-bench (@Sudha247 @talex5 #500).
- Fix MDX tests on OCaml 5.1 (@talex5 #526).
- Add stress test for spawning processes (@talex5 #519).
This was an attempt to track down the https://github.com/ocaml/ocaml/issues/12253 signals bug.
- `Eio.Process.pp_status` should be polymorphic (@talex5 #518).
- eio_posix: probe for existence of some flags (@talex5 #507, reported by @hannesm).
FreeBSD 12 didn't have `O_DSYNC`. Also, add `O_RESOLVE_BENEATH` and `O_PATH` if available.
- Fix race in ctf tests (@talex5 #493).
## v0.9
New features:
- Add eio_posix backend (@talex5 @haesbaert #448 #477, reviewed by @avsm @patricoferris @polytypic).
This replaces eio_luv on all platforms except Windows (which will later switch to its own backend). It is a lot faster, provides access to more modern features (such as `openat`), and can safely share OS resources between domains.
- Add subprocess support (@patricoferris @talex5 #461 #464 #472, reviewed by @haesbaert @avsm).
This is the low-level API support for eio_linux and eio_posix. A high-level cross-platform API will be added in the next release.
- Add `Fiber.fork_seq` (@talex5 #460, reviewed by @avsm).
This is a light-weight alternative to using a single-producer, single-consumer, 0-capacity stream, similar to a Python generator function.
Bug fixes:
- eio_linux: make it safe to share FDs across domains (@talex5 #440, reviewed by @haesbaert).
It was previously not safe to share file descriptors between domains because if one domain used an FD just as another was closing it, and the FD got reused, then the original operation could act on the wrong file.
- eio_linux: release uring if Linux is too old (@talex5 #476).
Avoids a small resource leak.
- eio_linux: improve error handling creating pipes and sockets (@talex5 #474, spotted by @avsm).
If we get an error (e.g. too many FDs) then report it to the calling fiber, instead of exiting the event loop.
- eio_linux: wait for uring to finish before exiting (@talex5 #470, reviewed by @avsm).
If the main fiber raised an exception then it was possible to exit while a cancellation operation was still in progress.
- eio_main: make `EIO_BACKEND` handling more uniform (@talex5 #447).
Previously this environment variable was only used on Linux. Now all platforms check it.
- Tell dune about `EIO_BACKEND` (@talex5 #442).
If this changes, dune needs to re-run the tests.
- eio_linux: add some missing close-on-execs (@talex5 #441).
- eio_linux: `read_exactly` fails to update file offset (@talex5 #438).
- Work around dune `enabled_if` bug on non-Linux systems (@polytypic #475, reviewed by @talex5).
- Use raw system call of `getrandom` for glibc versions before 2.25 (@zenfey #482).
Documentation:
- Add `HACKING.md` with hints for working on Eio (@talex5 #443, reviewed by @avsm @polytypic).
- Improve worker pool example (@talex5 #454).
- Add more Conditions documentation (@talex5 #436, reviewed by @haesbaert).
This adds a discussion of conditions to the README and provides examples using them to handle signals.
- Condition: fix the example in the docstring (@avsm #468).
Performance:
- Add a network benchmark using an HTTP-like protocol (@talex5 #478, reviewed by @avsm @patricoferris).
- Add a benchmark for reading from `/dev/zero` (@talex5 #439).
Other changes:
- Add CI for macOS (@talex5 #452).
- Add tests for `pread`, `pwrite` and `readdir` (@talex5 #451).
- eio_linux: split into multiple files (@talex5 #465 #466, reviewed by @avsm).
- Update Dockerfile (@talex5 #471).
- Use dune.3.7.0 (@patricoferris #457).
- Mint exclusive IDs across domains (@TheLortex #480, reported by @haesbaert, reviewed by @talex5).
The tracing currently only works with a single domain anyway, but this will change when OCaml 5.1 is released.
## v0.8.1
Some build fixes:
- Fix build on various architectures (@talex5 #432).
- Work around dune `%{system}` bug.
- eio_luv: fix `max_luv_buffer_size` on 32-bit platforms.
- Add missing test-dependency on MDX (@talex5 #430).
## v0.8
New features:
- Add `Eio.Net.run_server` (@bikallem @talex5 #408).
Runs an accept loop in one or more domains, with cancellation and graceful shutdown,
and an optional maximum number of concurrent connections.
- Add `Buf_read.BE` and `LE` parsers (@Cjen1 #399).
Parse numbers in various binary formats.
- Add `Eio.Buf_read.uint8` (@talex5 #418).
Performance:
- Make `Eio.Condition` lock-free (@talex5 #397 #381).
In addition to being faster, this allows using conditions in signal handlers.
- Make `Eio.Semaphore` lock-free (@talex5 @polytypic #398).
- Make `Eio.Stream` lock-free when the capacity is zero (@talex5 #413 #411).
- Make `Eio.Promise` lock-free (@talex5 #401).
Bug fixes:
- eio_linux: call `Uring.submit` as needed (@talex5 @bikallem #428).
Previously, we could fail to submit a job promptly because the SQE queue was full.
- Fix luv signals (@haesbaert #412).
`libuv` automatically retries polling if it gets `EINTR`, without giving OCaml signal handlers a chance to run.
- eio_luv: fix some resource leaks (@talex5 @patricoferris #421).
- eio_luv: fix "unavailable signal" error on Windows (@talex5 #420, reported by @nojb).
- Fix `Buf_write.BE.uint48` and `LE.uint48` (@adatario #418).
Documentation:
- Add example programs (@talex5 #389).
- Update network examples to use `run_server` (@talex5 #417).
- Add a warning to the tutorial about `Fiber.first` (@talex5 #394).
- Clarify the epoch used for `Eio.Time.now` (@bikallem #395).
- Describe `secure_random` as an infinite source (@patricoferris #426).
- Update README for OCaml 5 release (@talex5 #384 #391 #393).
Other changes:
- Delay setting `SIGPIPE` handler until the `run` function is called (@talex5 #420).
- Remove debug-level logging (@talex5 #403).
- eio-luv: improve `process.md` test (@smondet #414).
- Update to Dune 3 (@talex5 #410).
- Remove test dependency on Astring (@talex5 #402 #404).
- Simplify cancellation logic (@talex5 #396).
- time: `Mtime.Spand.to_s` has been deprecated in mtime 2.0.0 (@bikallem #385).
## v0.7
API changes:
- Unify IO errors as `Eio.Io` (@talex5 #378).
This makes it easy to catch and log all IO errors if desired.
The exception payload gives the type and can be used for matching specific errors.
It also allows attaching extra information to exceptions, and various functions were updated to do this.
- Add `Time.Mono` for monotonic clocks (@bikallem @talex5 #338).
Using the system clock for timeouts, etc can fail if the system time is changed during the wait.
- Allow datagram sockets to be created without a source address (@bikallem @haesbaert #360).
The kernel will allocate an address in this case.
You can also now control the `reuse_addr` and `reuse_port` options.
- Add `File.stat` and improve `Path.load` (@haesbaert @talex5 #339).
`Path.load` now uses the file size as the initial buffer size.
- Add `Eio_unix.pipe` (@patricoferris #350).
This replaces `Eio_linux.pipe`.
- Avoid short reads from `getrandom(2)` (@haesbaert #344).
Guards against buggy user code that might not handle this correctly.
- Rename `Flow.read` to `Flow.single_read` (@talex5 #353).
This is a low-level function and it is easy to use it incorrectly by ignoring the possibility of short reads.
Bug fixes:
- Eio_luv: Fix non-tail-recursive continue (@talex5 #378).
Affects the `Socket_of_fd` and `Socketpair` effects.
- Eio_linux: UDP sockets were not created close-on-exec (@talex5 #360).
- Eio_linux: work around io_uring non-blocking bug (@haesbaert #327 #355).
The proper fix should be in Linux 6.1.
- `Eio_mock.Backend`: preserve backtraces from `main` (@talex5 #349).
- Don't lose backtrace in `Switch.run_internal` (@talex5 #369).
Documentation:
- Use a proper HTTP response in the README example (@talex5 #377).
- Document that read_dir excludes "." and ".." (@talex5 #379).
- Warn about both operations succeeding in `Fiber.first` (@talex5 #358, reported by @iitalics).
- Update README for OCaml 5.0.0~beta2 (@talex5 #375).
Backend-specific changes:
- Eio_luv: add low-level process support (@patricoferris #359).
A future release will add Eio_linux support and a cross-platform API for this.
- Expose `Eio_luv.Low_level.Stream.write` (@patricoferris #359).
- Expose `Eio_luv.Low_level.get_loop` (@talex5 #371).
This is needed if you want to create resources directly and then use them with Eio_luv.
- `Eio_linux.Low_level.openfile` is gone (@talex5 #378).
It was just left-over test code.
## v0.6
Changes:
- Update to OCaml 5.0.0~beta1 (@anmonteiro @talex5 #346).
- Add API for seekable read/writes (@nojb #307).
- Add `Flow.write` (@haesbaert #318).
This provides an optimised alternative to `copy` in the case where you are writing from a buffer.
- Add `Net.with_tcp_connect` (@bikallem #302).
Convenience function for opening a TCP connection.
- Add `Eio.Time.Timeout` (@talex5 #320).
Makes it easier to pass timeouts around.
- Add `Eio_mock.Clock` (@talex5 #328).
Control time in tests.
- Add `Buf_read.take_while1` and `skip_while1` (@bikallem #309).
These fail if no characters match.
- Make the type parameter for `Promise.t` covariant (@anmonteiro #300).
- Move list functions into a dedicated submodule (@raphael-proust #315).
- Direct implementation of `Flow.source_string` (@c-cube #317).
Slightly faster.
Bug fixes:
- `Condition.broadcast` must interlock as well (@haesbaert #324).
- Split the reads into no more than 2^32-1 for luv (@haesbaert @talex5 @EduardoRFS #343).
Luv uses a 32 bit int for buffer sizes and wraps if the value passed is too big.
- eio_luv: allow `Net.connect` to be cancelled (@talex5 @nojb #311).
- eio_main: Use dedicated log source (@anmonteiro #326).
- linux_eio: fix kernel version number in log message (@talex5 @nojb #314).
- Account for stack differences in the socketpair test (issue #312) (@haesbaert #313).
Documentation:
- Add Best Practices section to README (@talex5 #299).
- Documentation improvements (@talex5 #295 #337).
## v0.5
New features:
- Add `Eio.Condition` (@TheLortex @talex5 #277).
Allows a fiber to wait for some condition to become true.
- Add `Eio.Net.getaddrinfo` and `getnameinfo` (@bikallem @talex5 #278 #288 #291).
Convert between host names and addresses.
- Add `Eio.Debug` (@talex5 #276).
Currently, this allows overriding the `traceln` function.
- `Buf_write.create`: make switch optional (@talex5 #283).
This makes things easier for people porting code from Faraday.
Bug fixes:
- Allow sharing of libuv poll handles (@patricoferris @talex5 #279).
Luv doesn't allow two callers to watch the same file handle, so we need to handle that in Eio.
Other changes:
- Upgrade to uring 0.4 (@talex5 #290).
- Mention `Mutex`, `Semaphore` and `Condition` in the README (@talex5 #281).
## v0.4
Note: Eio 0.4 drops compatibility with OCaml 4.12+domains. Use OCaml 5.0.0~alpha1 instead.
API changes:
- `Eio.Dir` has gone. Use `Eio.Path` instead (@talex5 #266 #270).
- `Eio_unix.FD.{take,peek}` were renamed to `take_opt`/`peek_opt` to make way for non-optional versions.
New features:
- Fiber-local storage (@SquidDev #256).
Attach key/value bindings to fibers. These are inherited across forks.
- `Eio.Path.{unlink,rmdir,rename}` (@talex5 #264 #265).
- `Eio_main.run` can now return a value (@talex5 #263).
This is useful for e.g. cmdliner.
- `Eio_unix.socketpair` (@talex5 #260).
- `Fiber.fork_daemon` (@talex5 #252).
Create a helper fiber that does not prevent the switch from exiting.
- Add `Fiber.{iter,map,filter,fiter_map}` (@talex5 #248 #250).
These are concurrent versions of the corresponding operations in `List`.
Bug fixes:
- Fix scheduling fairness in luv backend (@talex5 #269).
- Implement remaining shutdown commands for luv (@talex5 #268).
- Fix IPv6 support with uring backend (@haesbaert #261 #262).
- Use `Eio.Net.Connection_reset` exception in more places (@talex5 #257).
- Report use of closed FDs better (@talex5 #255).
Using a closed FD could previously cause the whole event loop to exit.
- Some fixes for cancellation (@talex5 #254).
- Ensure `Buf_write` still flushes if an exception is raised (@talex5 #246).
- Do not allow close on `accept_fork` socket (@talex5 #245).
Documentation:
- Document integrations with Unix, Lwt and Async (@talex5 #247).
- Add a Dockerfile for easy testing (@talex5 #224).
## v0.3
API changes:
- `Net.accept_sub` is deprecated in favour of `accept_fork` (@talex5 #240).
`Fiber.fork_on_accept`, which it used internally, has been removed.
- Allow short writes in `Read_source_buffer` (@talex5 #239).
The reader is no longer required to consume all the data in one go.
Also, add `Linux_eio.Low_level.writev_single` to expose this behaviour directly.
- `Eio.Unix_perm` is now `Eio.Dir.Unix_perm`.
New features:
- Add `Eio.Mutex` (@TheLortex @talex5 #223).
- Add `Eio.Buf_write` (@talex5 #235).
This is a buffered writer for Eio sinks, based on Faraday.
- Add `Eio_mock` library for testing (@talex5 #228).
At the moment it has mock flows and networks.
- Add `Eio_mock.Backend` (@talex5 #237 #238).
Allows running tests without needing a dependency on eio_main.
Also, as it is single-threaded, it can detect deadlocks in test code instead of just hanging.
- Add `Buf_read.{of_buffer, of_string, parse_string{,_exn}, return}` (@talex5 #225).
- Add `<*>` combinator to `Buf_read.Syntax` (@talex5 #227).
- Add `Eio.Dir.read_dir` (@patricoferris @talex5 #207 #218 #219)
Performance:
- Add `Buf_read` benchmark and optimise it a bit (@talex5 #230).
- Inline `Buf_read.consume` to improve performance (@talex5 #232).
Bug fixes / minor changes:
- Allow IO to happen even if a fiber keeps yielding (@TheLortex @talex5 #213).
- Fallback for `traceln` without an effect handler (@talex5 #226).
`traceln` now works outside of an event loop too.
- Check for cancellation when creating a non-protected child context (@talex5 #222).
- eio_linux: handle EINTR when calling `getrandom` (@bikallem #212).
- Update to cmdliner.1.1.0 (@talex5 #190).
## v0.2 ## v0.2
- Add support for UDP (@patricoferris #171). - Add support for UDP (@patricoferris #171).

14
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,14 @@
# 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:
* Patrick Ferris <patrick [at] sirref [dot] org>
* Sudha Parimala <sudha [at] tarides [dot] com>
* Thomas Leonard <thomasleonard [at] tarides [dot] com>

16
Dockerfile Normal file
View File

@ -0,0 +1,16 @@
FROM ocaml/opam:debian-11-ocaml-5.2
# Make sure we're using opam-2.1:
RUN sudo ln -sf /usr/bin/opam-2.1 /usr/bin/opam
# Ensure opam-repository is up-to-date:
RUN cd opam-repository && git pull -q origin 97de3378749cf8d2d70a5d710d310e5cc17c9dea && opam update
# Install utop for interactive use:
RUN opam install utop fmt
# Install Eio's dependencies (adding just the opam files first to help with caching):
RUN mkdir eio
WORKDIR eio
COPY *.opam ./
RUN opam pin --with-version=dev . -yn
RUN opam install --deps-only eio_main eio_linux eio
# Build Eio:
COPY . ./
RUN opam install eio_main

149
HACKING.md Normal file
View File

@ -0,0 +1,149 @@
## Installing Eio from Git
If you want to run the latest development version from Git, run these commands:
```
git clone https://github.com/ocaml-multicore/eio.git
cd eio
opam pin -yn .
opam install eio_main
```
## Layout of the code
`lib_eio/core` contains the core logic about fibers, promises, switches, etc.
`lib_eio` extends this with e.g. streams, buffered readers, buffered writers,
and a load of types for OS resources (files, networks, etc).
There is one directory for each backend (e.g. `eio_linux`).
Each backend provides a scheduler that integrates with a particular platform,
and implements some or all of the cross-platform resource APIs.
For example, `eio_linux` implements the network interface using `io_uring` to send data.
`lib_main` just selects an appropriate backend for the current system.
## Writing a backend
It's best to start by reading `lib_eio/mock/backend.ml`, which implements a mock backend with no actual IO.
You can then read one of the real backends to see how to integrate this with the OS.
Most backends are built in two layers:
- A "low-level" module directly wraps the platform's own API, just adding support for suspending fibers for concurrency
and basic safety features (such wrapping `Unix.file_descr` to prevent use-after-close races).
- An implementation of the cross-platform API (as defined in the `eio` package) that uses the low-level API internally.
This should ensure that errors are reported using the `Eio.Io` exception.
`eio_posix` is the best one to look at first:
- `lib_eio_posix/sched.ml` is similar to the mock scheduler, but extended to interact with the OS kernel.
- `lib_eio_posix/low_level.ml` provides fairly direct wrappers of the standard POSIX functions,
but using `sched.ml` to suspend and resume instead of blocking the whole domain.
- `lib_eio_posix/net.ml` implements the cross-platform API using the low-level API.
For example, it converts Eio network addresses to Unix ones.
Likewise, `fs.ml` implements the cross-platform file-system APIs, etc.
- `lib_eio_posix/eio_posix.ml` provides the main `run` function.
It runs the scheduler, passing to the user's `main` function an `env` object for the cross-platform API functions.
When writing a backend, it's best to write the main loop in OCaml rather than delegate that to a C function.
Some particular things to watch out for:
- If a system call returns `EINTR`, you must switch back to OCaml
(`caml_leave_blocking_section`) so that the signal can be handled. Some C
libraries just restart the function immediately and this will break signal
handling (on systems that have signals).
- If C code installs a signal handler, it *must* use the alt stack (`SA_ONSTACK`).
Otherwise, signals handlers will run on the fiber stack, which is too small and will result in memory corruption.
- Effects cannot be performed over a C function.
So, if the user installs an effect handler and then calls a C mainloop, and the C code invokes a callback,
the callback cannot use the effect handler.
This isn't a problem for Eio itself (Eio's effect handler is installed inside the mainloop),
but it can break programs using effects in other ways.
## Tests
Eio has tests in many places...
### Cross-platform unit tests
These are in the top-level `tests` directory.
They are run against whichever backend `Eio_main.run` selects, and therefore must get the same result for all backends.
### Concurrency primitives
`lib_eio/tests` tests some internal data structures, such as the lock-free cells abstraction.
The `.md` files in that directory provide a simple walk-through to demonstrate the basic operation,
while `lib_eio/tests/dscheck` uses [dscheck][] to perform exhaustive testing of all atomic interleavings.
At the time of writing, dscheck has some performance problems that make it unusable by default, so
you must use the version in https://github.com/ocaml-multicore/dscheck/pull/22 instead.
### Benchmarks
The `bench` directory contains various speed tests.
`make bench` is a convenient way to run all of them.
This is useful to check for regressions.
If you want to contibute an optimisation, please add a benchmark so that we can measure the improvement.
If you are changing something, make sure the benchmark doesn't get significantly worse.
### Stress and fuzz testing
The `fuzz` directory uses afl-fuzz to search for bugs.
Using it properly requires an instrumented version of the OCaml compiler
(see https://v2.ocaml.org/manual/afl-fuzz.html for instructions).
The `dune` build rules don't use afl-fuzz; they just do a few random tests and then stop.
To run e.g. the `fuzz_buf_read` tests with afl-fuzz:
```
mkdir input
date > input/seed
afl-fuzz -m 1000 -i input -o output ./_build/default/fuzz/fuzz_buf_read.exe @@
```
- `Fork server handshake failed` indicates that you are not using an AFL-enabled version of OCaml.
- `The current memory limit (75.0 MB) is too restrictive` means you forgot to use `-m`.
The `stress` directory contains stress tests (that try to trigger races by brute force).
### Backend-specific tests
There are also backend-specific tests, e.g.
- `lib_eio_linux/tests`
- `lib_eio_luv/tests`
Use these for tests that only make sense for one platform.
### Formal verification
Some parts of Eio have been formally verified:
- https://github.com/addap/master-thesis/tree/main/documents [[video](https://discuss.ocaml.org/t/video-verifying-an-effect-based-cooperative-concurrency-scheduler-in-iris-by-adrian-dapprich/13825)]
- https://github.com/clef-men/zebre/tree/main/theories/eio
## Code formatting
Eio's code is indented using ocp-indent.
When making PRs, please do not apply other formatting tools to existing code unrelated to your PR.
Try to avoid making unnecessary changes; this makes review harder and clutters up the Git history.
`ocamlformat` may be useful to get badly messed up code to a baseline unformatted state,
from which human formatting can be added where needed.
## AI-generated Code
Contributing to Eio should not be done _solely_ using "AI tools" such as ChatGPT. This is for a few reasons:
1. **It obfuscates how you think**. Purely AI-generated code tells us little about how you think and the problems you might be having. This makes it harder to provide good feedback on PRs and issues.
2. **It is often more work to review**. Particularly for the OCaml ecosystem and libraries like Eio, it seems that these tools are not very good and generate a lot of believable code that is in actual fact completely wrong. PR comments and the code submitted with them can say completely different things.
3. **It is a grey area for licensing**. Models like ChatGPT have been trained on lots of code with different licenses and has been known to simply copy code as an answer to a prompt. We would like to avoid this headache as best we can.
Use AI tools, if you wish, to help you understand OCaml and Eio. Do not offload all of the work of a PR or a comment to these tools.
[dscheck]: https://github.com/ocaml-multicore/dscheck

View File

@ -33,3 +33,38 @@ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
The `Eio.Buf_write` module is based on Faraday by Inhabited Type LLC,
which has the following license (BSD-3-clause):
Copyright (c) 2016, Inhabited Type LLC
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the author nor the names of his contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,16 +1,25 @@
.PHONY: all bench .PHONY: all bench stress
all: all:
dune build @runtest @all dune build @runtest @all
bench: bench:
dune exec -- ./bench/bench_yield.exe dune exec -- ./bench/main.exe
dune exec -- ./bench/bench_promise.exe
dune exec -- ./bench/bench_stream.exe
dune exec -- ./bench/bench_semaphore.exe
dune exec -- ./bench/bench_cancel.exe
dune exec -- ./lib_eio_linux/tests/bench_noop.exe
test_luv: test_posix:
rm -rf _build EIO_BACKEND=posix dune runtest
EIO_BACKEND=luv dune runtest
dscheck:
dune exec -- ./lib_eio/tests/dscheck/test_condition.exe
dune exec -- ./lib_eio/tests/dscheck/test_rcfd.exe
dune exec -- ./lib_eio/tests/dscheck/test_sync.exe
dune exec -- ./lib_eio/tests/dscheck/test_semaphore.exe
dune exec -- ./lib_eio/tests/dscheck/test_cells.exe
stress:
dune exec -- ./stress/stress_proc.exe
dune exec -- ./stress/stress_semaphore.exe
dune exec -- ./stress/stress_release.exe
docker:
docker build -t eio .

1480
README.md

File diff suppressed because it is too large Load Diff

14
bench.Dockerfile Normal file
View File

@ -0,0 +1,14 @@
FROM ocaml/opam:debian-11-ocaml-5.2
# Make sure we're using opam-2.1:
RUN sudo ln -sf /usr/bin/opam-2.1 /usr/bin/opam
# Ensure opam-repository is up-to-date:
RUN cd opam-repository && git pull -q origin 97de3378749cf8d2d70a5d710d310e5cc17c9dea && opam update
# Install Eio's dependencies (adding just the opam files first to help with caching):
RUN mkdir eio
WORKDIR eio
COPY *.opam ./
RUN opam pin --with-version=dev . -yn
RUN opam install eio_main yojson
# Build the benchmarks:
COPY . ./
RUN opam exec -- dune build ./bench

18
bench/bench_buf_read.ml Normal file
View File

@ -0,0 +1,18 @@
module R = Eio.Buf_read
let run _env =
let test_data = String.make 100_000_000 'x' in
let r = R.of_string test_data in
let t0 = Unix.gettimeofday () in
let i = ref 0 in
try
while true do
assert (R.any_char r = 'x');
incr i
done;
assert false
with End_of_file ->
let t1 = Unix.gettimeofday () in
let time = t1 -. t0 in
let bytes_per_second = float (String.length test_data) /. time in
[Metric.create "any_char" (`Float bytes_per_second) "bytes/s" "Parsing a long string one character at a time"]

View File

@ -20,8 +20,12 @@ let run_bench ?domain_mgr ~clock () =
| Some dm -> Eio.Domain_manager.run dm (fun () -> run_sender stream) | Some dm -> Eio.Domain_manager.run dm (fun () -> run_sender stream)
| None -> run_sender stream | None -> run_sender stream
in in
let name str =
match domain_mgr with
| Some _ -> str ^ "/separate domains"
| None -> str ^ "/single domain"
in
Gc.full_major (); Gc.full_major ();
let _minor0, prom0, _major0 = Gc.counters () in
let t0 = Eio.Time.now clock in let t0 = Eio.Time.now clock in
try try
Switch.run (fun sw -> Switch.run (fun sw ->
@ -39,17 +43,17 @@ let run_bench ?domain_mgr ~clock () =
let t1 = Eio.Time.now clock in let t1 = Eio.Time.now clock in
let time_total = t1 -. t0 in let time_total = t1 -. t0 in
let time_per_iter = time_total /. float n_iters in let time_per_iter = time_total /. float n_iters in
let _minor1, prom1, _major1 = Gc.counters () in Metric.create
let prom = prom1 -. prom0 in (name "take-first")
Printf.printf "%11b, %7.2f, %13.4f\n%!" (domain_mgr <> None) (1e9 *. time_per_iter) (prom /. float n_iters) (`Float (1e9 *. time_per_iter)) "ns"
"Time to take from one of two streams"
let main ~domain_mgr ~clock = let main ~domain_mgr ~clock =
Printf.printf "use_domains, ns/iter, promoted/iter\n%!"; let m1 = run_bench ~clock () in
run_bench ~clock (); let m2 = run_bench ~domain_mgr ~clock () in
run_bench ~domain_mgr ~clock () [m1; m2]
let () = let run env =
Eio_main.run @@ fun env ->
main main
~domain_mgr:(Eio.Stdenv.domain_mgr env) ~domain_mgr:(Eio.Stdenv.domain_mgr env)
~clock:(Eio.Stdenv.clock env) ~clock:(Eio.Stdenv.clock env)

65
bench/bench_condition.ml Normal file
View File

@ -0,0 +1,65 @@
open Eio.Std
(* A publisher keeps updating a counter and signalling a condition.
Two consumers read the counter whenever they get a signal.
The producer stops after signalling [target], and the consumers stop after seeing it. *)
let n_iters = 100
let target = 100000
let run_publisher cond v =
for i = 1 to target do
Atomic.set v i;
(* traceln "set %d" i; *)
Eio.Condition.broadcast cond
done
let run_consumer cond v =
try
while true do
Fiber.both
(fun () -> Eio.Condition.await_no_mutex cond)
(fun () ->
let current = Atomic.get v in
(* traceln "saw %d" current; *)
if current = target then raise Exit
)
done
with Exit -> ()
let run_bench ?domain_mgr ~clock () =
let cond = Eio.Condition.create () in
let v = Atomic.make 0 in
let run_consumer () =
match domain_mgr with
| Some dm -> Eio.Domain_manager.run dm (fun () -> run_consumer cond v)
| None -> run_consumer cond v
in
let name str =
match domain_mgr with
| Some _ -> str ^ "_domain"
| None -> str
in
Gc.full_major ();
let t0 = Eio.Time.now clock in
for _ = 1 to n_iters do
Fiber.all [
run_consumer;
run_consumer;
(fun () -> run_publisher cond v);
];
done;
let t1 = Eio.Time.now clock in
let time_total = t1 -. t0 in
let time_per_iter = time_total /. float n_iters in
Metric.create (name "broadcast") (`Float (1e3 *. time_per_iter)) "ms" "Time to signal a new value"
let main ~domain_mgr ~clock = [
run_bench ~clock ();
run_bench ~domain_mgr ~clock ();
]
let run env =
main
~domain_mgr:(Eio.Stdenv.domain_mgr env)
~clock:(Eio.Stdenv.clock env)

44
bench/bench_copy.ml Normal file
View File

@ -0,0 +1,44 @@
(* A client opens a connection to an echo service and sends a load of data via it. *)
open Eio.Std
let chunk_size = 1 lsl 16
let n_chunks = 10000
let n_bytes = n_chunks * chunk_size
let run_client sock =
Fiber.both
(fun () ->
let chunk = Cstruct.create chunk_size in
for _ = 1 to n_chunks do
Eio.Flow.write sock [chunk]
done;
Eio.Flow.shutdown sock `Send
)
(fun () ->
let chunk = Cstruct.create chunk_size in
for _ = 1 to n_chunks do
Eio.Flow.read_exact sock chunk
done
)
let time name service =
Switch.run ~name @@ fun sw ->
let client_sock, server_sock = Eio_unix.Net.socketpair_stream ~sw () in
let t0 = Unix.gettimeofday () in
Fiber.both
(fun () -> service server_sock)
(fun () -> run_client client_sock);
let t1 = Unix.gettimeofday () in
let time = t1 -. t0 in
let bytes_per_second = float n_bytes /. time in
traceln "%s: %.2f MB/s" name (bytes_per_second /. 1024. /. 1024.);
Metric.create name (`Float bytes_per_second) "bytes/s" (name ^ " Flow.copy")
let run _env =
[
time "default" (fun sock -> Eio.Flow.copy sock sock);
time "buf_read" (fun sock ->
let r = Eio.Buf_read.of_flow sock ~initial_size:(64 * 1024) ~max_size:(64 * 1024) |> Eio.Buf_read.as_flow in
Eio.Flow.copy r sock);
]

38
bench/bench_fd.ml Normal file
View File

@ -0,0 +1,38 @@
open Eio.Std
let time label len fn =
let t0 = Unix.gettimeofday () in
fn ();
let t1 = Unix.gettimeofday () in
Metric.create
label
(`Float (float len /. (t1 -. t0) /. (2. ** 30.))) "GB/s"
"Reading from /dev/zero using a single FD"
let main ~domain_mgr zero =
let iters = 100_000 in
let len = 64 * 1024 in
let n_fibers = 4 in
let n_domains = 4 in
let buf = Cstruct.create len in
let run1 () =
for _ = 1 to iters do Eio.Flow.read_exact zero buf done
in
[time "fibers:1" (iters * len) run1;
time (Fmt.str "fibers:%d" n_fibers) (iters * n_fibers * len) (fun () ->
Switch.run @@ fun sw ->
for _ = 1 to n_fibers do
Fiber.fork ~sw run1
done
);
time (Fmt.str "domains:%d" n_domains) (iters * n_domains * len) (fun () ->
Switch.run @@ fun sw ->
for _ = 1 to n_domains do
Fiber.fork ~sw (fun () -> Eio.Domain_manager.run domain_mgr run1)
done
)]
let ( / ) = Eio.Path.( / )
let run env =
Eio.Path.with_open_in (env#fs / "/dev/zero") (main ~domain_mgr:env#domain_mgr)

26
bench/bench_fstat.ml Normal file
View File

@ -0,0 +1,26 @@
open Eio.Std
let ( / ) = Eio.Path.( / )
let n_stat = 100000
let run_fiber file =
for _ = 1 to n_stat do
let info = (Eio.File.stat file).kind in
assert (info = `Regular_file)
done
let run env =
Eio.Path.with_open_out ~create:(`If_missing 0o600) (env#cwd / "test-stat") @@ fun file ->
[1; 10] |> List.map (fun par ->
let t0 = Unix.gettimeofday () in
Switch.run (fun sw ->
for _ = 1 to par do
Fiber.fork ~sw (fun () -> run_fiber file)
done
);
let t1 = Unix.gettimeofday () in
let stat_per_s = float (n_stat * par) /. (t1 -. t0) in
let label = Printf.sprintf "n=%d fibers=%d" n_stat par in
Metric.create label (`Float stat_per_s) "stat/s" "Call fstat on an open file"
)

107
bench/bench_http.ml Normal file
View File

@ -0,0 +1,107 @@
(* A multi-domain server handles HTTP-like requests from many clients running across multiple domains. *)
open Eio.Std
(* Note: this is not a real HTTP parser! *)
let key_char = function
| 'A'..'Z' | 'a'..'z' | '-' -> true
| _ -> false
let parse_headers r =
let len = ref (-1) in
let rec aux () =
let key = Eio.Buf_read.take_while key_char r in
if key = "" then Eio.Buf_read.string "\r\n" r
else (
Eio.Buf_read.char ':' r;
let value = Eio.Buf_read.line r in
if key = "Content-Length" then len := int_of_string (String.trim value);
aux ()
)
in
aux ();
!len
let handle_connection conn _addr =
Eio.Buf_write.with_flow conn @@ fun w ->
let rec requests r =
let _req = Eio.Buf_read.line r in
let len = parse_headers r in
let body = Eio.Buf_read.take len r in
let response = body ^ " / received" in
Eio.Buf_write.string w "HTTP/1.1 200 OK\r\n";
Eio.Buf_write.string w (Printf.sprintf "Content-Length: %d\r\n" (String.length response));
Eio.Buf_write.string w "\r\n";
Eio.Buf_write.string w response;
if not (Eio.Buf_read.at_end_of_input r) then requests r
in
Eio.Buf_read.parse_exn requests conn ~max_size:max_int
let run_client ~n_requests id conn =
let total = ref 0 in
let r = Eio.Buf_read.of_flow conn ~max_size:max_int in
Eio.Buf_write.with_flow conn @@ fun w ->
for i = 1 to n_requests do
let msg = Printf.sprintf "%s / request %d" id i in
Eio.Buf_write.string w "POST / HTTP/1.1\r\n";
Eio.Buf_write.string w "Host: localhost:8085\r\n";
Eio.Buf_write.string w "User-Agent: bench_server\r\n";
Eio.Buf_write.string w "Connection: keep-alive\r\n";
Eio.Buf_write.string w (Printf.sprintf "Content-Length: %d\r\n" (String.length msg));
Eio.Buf_write.string w "\r\n";
Eio.Buf_write.string w msg;
let status = Eio.Buf_read.line r in
assert (status = "HTTP/1.1 200 OK");
let len = parse_headers r in
let body = Eio.Buf_read.take len r in
assert (body = msg ^ " / received");
incr total
done;
!total
let main net domain_mgr ~n_client_domains ~n_server_domains ~n_connections_per_domain ~n_requests_per_connection =
let total = Atomic.make 0 in
let t0 = Unix.gettimeofday () in
Switch.run ~name:"main" (fun sw ->
let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 8085) in
let backlog = n_connections_per_domain * n_client_domains in
let server_socket = Eio.Net.listen ~reuse_addr:true ~backlog ~sw net addr in
Fiber.fork_daemon ~sw (fun () ->
Eio.Net.run_server server_socket handle_connection
~additional_domains:(domain_mgr, n_server_domains - 1)
~on_error:raise
);
for domain = 1 to n_client_domains do
Fiber.fork ~sw (fun () ->
Eio.Domain_manager.run domain_mgr (fun () ->
Switch.run ~name:"client-domain" @@ fun sw ->
for i = 1 to n_connections_per_domain do
Fiber.fork ~sw (fun () ->
let id = Printf.sprintf "domain %d / conn %d" domain i in
let conn = Eio.Net.connect ~sw net addr in
let requests = run_client ~n_requests:n_requests_per_connection id conn in
ignore (Atomic.fetch_and_add total requests : int)
)
done
)
)
done
);
let t1 = Unix.gettimeofday () in
(* Fmt.pr "clients, servers, requests, requests/s@."; *)
let requests = n_connections_per_domain * n_client_domains * n_requests_per_connection in
assert (requests = Atomic.get total);
let req_per_s = float requests /. (t1 -. t0) in
Metric.create
(Printf.sprintf "requests:%d client-domains:%d server-domains:%d" requests n_client_domains n_server_domains)
(`Float req_per_s) "requests/s" "Request rate of a HTTP client/server system"
let run env =
let metrics =
main env#net env#domain_mgr
~n_client_domains:4
~n_server_domains:4
~n_connections_per_domain:25
~n_requests_per_connection:1000
in
[metrics]

54
bench/bench_mutex.ml Normal file
View File

@ -0,0 +1,54 @@
open Eio.Std
let v = ref 0
let run_worker ~iters_per_thread mutex =
for _ = 1 to iters_per_thread do
Eio.Mutex.lock mutex;
let x = !v in
v := x + 1;
Fiber.yield ();
assert (!v = x + 1);
v := x;
Eio.Mutex.unlock mutex;
done
let run_bench ~domain_mgr ~clock ~use_domains ~iters_per_thread ~threads =
let mutex = Eio.Mutex.create () in
Gc.full_major ();
let t0 = Eio.Time.now clock in
Switch.run (fun sw ->
for _ = 1 to threads do
Fiber.fork ~sw (fun () ->
if use_domains then (
Eio.Domain_manager.run domain_mgr @@ fun () ->
run_worker ~iters_per_thread mutex
) else (
run_worker ~iters_per_thread mutex
)
)
done
);
assert (!v = 0);
let t1 = Eio.Time.now clock in
let time_total = t1 -. t0 in
let n_iters = iters_per_thread * threads in
let time_per_iter = time_total /. float n_iters in
Metric.create
(Printf.sprintf "iterations=%d threads=%d" n_iters threads)
(`Float (1e9 *. time_per_iter)) "ns" "Time to update a shared counter"
let main ~domain_mgr ~clock =
[false, 1_000_000, 1;
false, 1_000_000, 2;
false, 100_000, 8;
true, 100_000, 1;
true, 10_000, 2;
true, 10_000, 8]
|> List.map (fun (use_domains, iters_per_thread, threads) ->
run_bench ~domain_mgr ~clock ~use_domains ~iters_per_thread ~threads)
let run env =
main
~domain_mgr:(Eio.Stdenv.domain_mgr env)
~clock:(Eio.Stdenv.clock env)

View File

@ -9,6 +9,12 @@ and response = {
next_request : request Promise.u; next_request : request Promise.u;
} }
(* Simulate other work in the domain, and also prevent it from going to sleep.
Otherwise, we're just measuring how long it takes the OS to wake a sleeping thread. *)
let rec spin () =
Fiber.yield ();
spin ()
(* A client and server exchange these payload values. (* A client and server exchange these payload values.
Each contains the current message and a resolver which the other party can use to reply. *) Each contains the current message and a resolver which the other party can use to reply. *)
@ -46,44 +52,57 @@ let bench_resolved ~clock ~n_iters =
t := !t + Promise.await p; t := !t + Promise.await p;
done; done;
let t1 = Eio.Time.now clock in let t1 = Eio.Time.now clock in
Printf.printf "Reading a resolved promise: %.3f ns\n%!" (1e9 *. (t1 -. t0) /. float n_iters); assert (!t = n_iters);
assert (!t = n_iters) Metric.create
"read-resolved"
(`Float (1e9 *. (t1 -. t0) /. float n_iters)) "ns"
"Time to read a resolved promise"
let run_bench ~domain_mgr ~clock ~use_domains ~n_iters = let maybe_spin v fn =
if v then Fiber.first spin fn
else fn ()
let run_bench ~domain_mgr ~spin ~clock ~use_domains ~n_iters =
let init_p, init_r = Promise.create () in let init_p, init_r = Promise.create () in
Gc.full_major (); Gc.full_major ();
let _minor0, prom0, _major0 = Gc.counters () in
let t0 = Eio.Time.now clock in let t0 = Eio.Time.now clock in
Fiber.both Fiber.both
(fun () -> (fun () ->
if use_domains then ( if use_domains then (
Eio.Domain_manager.run domain_mgr @@ fun () -> Eio.Domain_manager.run domain_mgr @@ fun () ->
run_server ~n_iters ~i:0 init_r maybe_spin spin (fun () -> run_server ~n_iters ~i:0 init_r)
) else ( ) else (
run_server ~n_iters ~i:0 init_r maybe_spin spin (fun () -> run_server ~n_iters ~i:0 init_r)
) )
) )
(fun () -> (fun () ->
run_client ~n_iters ~i:0 init_p maybe_spin spin (fun () -> run_client ~n_iters ~i:0 init_p)
); );
let t1 = Eio.Time.now clock in let t1 = Eio.Time.now clock in
let time_total = t1 -. t0 in let time_total = t1 -. t0 in
let time_per_iter = time_total /. float n_iters in let time_per_iter = time_total /. float n_iters in
let _minor1, prom1, _major1 = Gc.counters () in let domains_label =
let prom = prom1 -. prom0 in if use_domains then
Printf.printf "%11b, %8d, %8.2f, %13.4f\n%!" use_domains n_iters (1e9 *. time_per_iter) (prom /. float n_iters) if spin then "with-spin"
else "without-spin"
else "no"
in
Metric.create
(Printf.sprintf "iterations:%d domains:%s" n_iters domains_label)
(`Float (1e9 *. time_per_iter)) "ns"
"Time to round-trip a request/reply"
let main ~domain_mgr ~clock = let main ~domain_mgr ~clock =
bench_resolved ~clock ~n_iters:(10_000_000); let resolved = bench_resolved ~clock ~n_iters:(10_000_000) in
Printf.printf "use_domains, n_iters, ns/iter, promoted/iter\n%!"; let metrics = [false, false, 1_000_000;
[false, 1_000_000; true, true, 100_000;
true, 100_000] true, false, 100_000]
|> List.iter (fun (use_domains, n_iters) -> |> List.map (fun (use_domains, spin, n_iters) ->
run_bench ~domain_mgr ~clock ~use_domains ~n_iters run_bench ~domain_mgr ~spin ~clock ~use_domains ~n_iters
) ) in
resolved :: metrics
let () = let run env =
Eio_main.run @@ fun env ->
main main
~domain_mgr:(Eio.Stdenv.domain_mgr env) ~domain_mgr:(Eio.Stdenv.domain_mgr env)
~clock:(Eio.Stdenv.clock env) ~clock:(Eio.Stdenv.clock env)

View File

@ -1,55 +1,62 @@
open Eio.Std open Eio.Std
let run_sender ~n_iters ~batch_size ~ack sem = (* Simulate other work in the domain, and also prevent it from going to sleep.
for i = 1 to n_iters do Otherwise, we're just measuring how long it takes the OS to wake a sleeping thread. *)
Eio.Semaphore.release sem; let rec spin () =
if i mod batch_size = 0 then Fiber.yield ();
Eio.Semaphore.acquire ack spin ()
done
let run_bench ~domain_mgr ~clock ~use_domains ~n_iters ~batch_size = let run_bench ~domain_mgr ~clock ~use_domains ~n_iters ~n_resources =
let sem = Eio.Semaphore.make 0 in let n_workers = 4 in
let ack = Eio.Semaphore.make 0 in let sem = Eio.Semaphore.make n_resources in
Gc.full_major (); let n_pending = Atomic.make n_workers in
let _minor0, prom0, _major0 = Gc.counters () in let all_started, set_all_started = Promise.create () in
let t0 = Eio.Time.now clock in let t0 = ref 0.0 in
Fiber.both let run_worker ~n_iters sem =
(fun () -> Switch.run @@ fun sw ->
Fiber.fork_daemon ~sw spin;
if Atomic.fetch_and_add n_pending (-1) = 1 then (
Promise.resolve set_all_started ();
t0 := Eio.Time.now clock;
) else (
Promise.await all_started
);
for _ = 1 to n_iters do
Eio.Semaphore.acquire sem;
Fiber.yield ();
Eio.Semaphore.release sem
done
in
let run () =
if use_domains then ( if use_domains then (
Eio.Domain_manager.run domain_mgr @@ fun () -> Eio.Domain_manager.run domain_mgr @@ fun () ->
run_sender ~n_iters ~batch_size ~ack sem run_worker ~n_iters sem
) else ( ) else (
run_sender ~n_iters ~batch_size ~ack sem run_worker ~n_iters sem
) )
) in
(fun () -> Gc.full_major ();
for i = 1 to n_iters do Fiber.all (List.init n_workers (Fun.const run));
Eio.Semaphore.acquire sem;
if i mod batch_size = 0 then
Eio.Semaphore.release ack
done
);
let t1 = Eio.Time.now clock in let t1 = Eio.Time.now clock in
let time_total = t1 -. t0 in let time_total = t1 -. !t0 in
let time_per_iter = time_total /. float n_iters in let time_per_iter = time_total /. float n_iters in
let _minor1, prom1, _major1 = Gc.counters () in Metric.create
let prom = prom1 -. prom0 in (Printf.sprintf "iterations:%d resources:%d" n_iters n_resources)
Printf.printf "%11b, %8d, %3d, %8.2f, %13.4f\n%!" use_domains n_iters batch_size (1e9 *. time_per_iter) (prom /. float n_iters) (`Float (1e9 *. time_per_iter)) "ns"
"Time to acquire a semaphore, yeild, and release it"
let main ~domain_mgr ~clock = let main ~domain_mgr ~clock =
Printf.printf "use_domains, n_iters, batch, ns/iter, promoted/iter\n%!"; [false, 100_000, 2;
[false, 1_000_000, 1; false, 100_000, 3;
false, 1_000_000, 10; false, 100_000, 4;
false, 1_000_000, 100; true, 10_000, 2;
true, 100_000, 1; true, 10_000, 3;
true, 100_000, 10; true, 10_000, 4]
true, 100_000, 100] |> List.map (fun (use_domains, n_iters, n_resources) ->
|> List.iter (fun (use_domains, n_iters, batch_size) -> run_bench ~domain_mgr ~clock ~use_domains ~n_iters ~n_resources
run_bench ~domain_mgr ~clock ~use_domains ~n_iters ~batch_size
) )
let () = let run env =
Eio_main.run @@ fun env ->
main main
~domain_mgr:(Eio.Stdenv.domain_mgr env) ~domain_mgr:(Eio.Stdenv.domain_mgr env)
~clock:(Eio.Stdenv.clock env) ~clock:(Eio.Stdenv.clock env)

152
bench/bench_stat.ml Normal file
View File

@ -0,0 +1,152 @@
open Eio.Std
module Path = Eio.Path
let () = Random.init 3
let ( / ) = Eio.Path.( / )
module Bench_dir = struct
type t =
| Dir of { name : string; perm : int; children : t list }
| File of { name : string; size : int64; perm : int; }
let get_name = function Dir { name; _ } | File { name; _ } -> name
let get_children = function
| Dir { children; _ } -> children
| File _ -> invalid_arg "Files don't have children"
let compare a b = String.compare (get_name a) (get_name b)
let rec sort = function
| Dir ({ children; _ } as v) ->
let c = List.map sort children in
let c = List.stable_sort compare c in
Dir { v with children = c }
| File _ as f -> f
let rec size = function
| Dir { children; _ } ->
List.fold_left (fun acc v -> acc + size v) 0 children
| File _ -> 1
let rec pp ppf = function
| Dir { name; perm; children } ->
if children = [] then Fmt.pf ppf "dir %s (0o%o)" name perm else
Fmt.pf ppf "@[<v2>dir %s (0o%o)@ %a@]" name perm Fmt.(list ~sep:Fmt.cut pp) children
| File { name; size; perm } ->
Fmt.pf ppf "file %s (0o%o) %Lu" name perm size
let make fs t =
let rec aux iter fs = function
| Dir { name; perm; children } ->
let dir = fs / name in
Path.mkdir ~perm dir;
iter (aux List.iter dir) children
| File { name; size; perm } ->
let buf = Cstruct.create (Int64.to_int size) in
Path.with_open_out ~create:(`If_missing perm) (fs / name) (fun oc ->
Eio.Flow.write oc [ buf ]
)
in
aux Fiber.List.iter fs t
end
let with_tmp_dir ~fs prefix suffix fn =
Switch.run @@ fun sw ->
let dir = fs / Filename.temp_dir prefix suffix in
Switch.on_release sw (fun () -> Path.rmtree dir);
fn dir
let bench_stat root =
let rec aux level dir =
let { Eio.File.Stat.kind; perm; size; _ } = Path.stat ~follow:false dir in
match kind with
| `Directory ->
let items = Path.read_dir dir in
let map = if level > 3 then List.map else Fiber.List.map ?max_fibers:None in
let children = items |> map (fun f -> aux (level + 1) (dir / f)) in
let name = Path.native_exn dir |> Filename.basename in
Bench_dir.Dir { name; perm; children }
| `Regular_file ->
let name = Path.native_exn dir |> Filename.basename in
File { name; perm; size = Optint.Int63.to_int64 size }
| _ -> assert false
in
aux 1 root
let file name = Bench_dir.File { name; perm = 0o644; size = 128L }
let dir name children = Bench_dir.Dir { name; perm = 0o700; children }
let random_bench_dir ~n ~levels =
if levels < 1 then invalid_arg "Levels should be >= 1";
let rec loop root = function
| 1 -> (
match root with
| Bench_dir.Dir d ->
let leaf_files = List.init n (fun i -> file (Fmt.str "test-file-%i-%i" 1 i)) in
Bench_dir.Dir { d with children = leaf_files }
| _ -> failwith "Root is always expected to be a directory"
)
| level ->
match root with
| Bench_dir.Dir d ->
let files = List.init n (fun i -> file (Fmt.str "test-file-%i-%i" level i)) in
let dirs = List.init n (fun i -> dir (Fmt.str "test-dir-%i-%i" level i) []) in
let dirs = List.map (fun dir -> loop dir (level - 1)) dirs in
Bench_dir.Dir { d with children = dirs @ files }
| _ -> failwith "Root is always expected to be directory"
in
loop (dir "root" []) levels
let run_bench ~n ~levels ~root ~clock =
let dir = random_bench_dir ~levels ~n |> Bench_dir.sort in
traceln "Going to create %i files and directories" (Bench_dir.size dir);
let create_time =
let t0 = Eio.Time.now clock in
Bench_dir.make root dir;
let t1 = Eio.Time.now clock in
t1 -. t0
in
traceln "Created in %.2f s" create_time;
let bench () =
Gc.full_major ();
let stat0 = Gc.stat () in
let t0 = Eio.Time.now clock in
let res = bench_stat root in
let t1 = Eio.Time.now clock in
let stat1 = Gc.stat () in
match Bench_dir.sort res with
| Dir { children = [ dir' ]; _ } ->
assert (dir = dir');
let time_total = t1 -. t0 in
let minor_total = stat1.minor_words -. stat0.minor_words in
let major_total = stat1.major_words -. stat0.major_words in
time_total, minor_total, major_total
| _ -> failwith "Stat not the same as the spec"
in
let time, minor, major = bench () in
traceln "Statted in %.2f s" time;
let remove_time =
let t0 = Eio.Time.now clock in
let root = root / "root" in
Eio.Path.read_dir root |> Fiber.List.iter (fun item -> Eio.Path.rmtree (root / item));
Eio.Path.rmdir root;
let t1 = Eio.Time.now clock in
t1 -. t0
in
traceln "Removed in %.2f s" remove_time;
[
Metric.create "create-time" (`Float (1e3 *. create_time)) "ms" (Fmt.str "Time to create %i files and directories" (Bench_dir.size dir));
Metric.create "stat-time" (`Float (1e3 *. time)) "ms" (Fmt.str "Time to stat %i files and directories" (Bench_dir.size dir));
Metric.create "stat-minor" (`Float (1e-3 *. minor)) "kwords" (Fmt.str "Minor words allocated to stat %i files and directories" (Bench_dir.size dir));
Metric.create "stat-major" (`Float (1e-3 *. major)) "kwords" (Fmt.str "Major words allocated %i files and directories" (Bench_dir.size dir));
Metric.create "remove-time" (`Float (1e3 *. remove_time)) "ms" "Time to remove everything";
]
let run env =
let fs = Eio.Stdenv.fs env in
let clock = Eio.Stdenv.clock env in
with_tmp_dir ~fs "eio-bench-" "-stat" @@ fun root ->
run_bench ~n:20 ~levels:4 ~root ~clock

View File

@ -1,49 +1,90 @@
(* Some sender domains each run a bunch of fibers submitting items to a stream.
Some receiver domains each run a single fiber accepting items from the stream.
It also tests the single-domain case. *)
open Eio.Std open Eio.Std
let run_sender ~n_iters stream = let n_sender_fibers = 10 (* Concurrent sending fibers per sending domain *)
(* Simulate other work in the domain, and also prevent it from going to sleep.
Otherwise, we're just measuring how long it takes the OS to wake a sleeping thread. *)
let rec spin () =
Fiber.yield ();
spin ()
(* [n_fibers] fibers each send values [1..n_iters] to [stream]. *)
let run_sender ~n_fibers ~n_iters stream =
Switch.run @@ fun sw ->
Fiber.fork_daemon ~sw spin;
for _ = 1 to n_fibers do
Fiber.fork ~sw (fun () ->
for i = 1 to n_iters do for i = 1 to n_iters do
Eio.Stream.add stream i Eio.Stream.add stream i
done done
let run_bench ~domain_mgr ~clock ~use_domains ~n_iters ~capacity =
let stream = Eio.Stream.create capacity in
Gc.full_major ();
let _minor0, prom0, _major0 = Gc.counters () in
let t0 = Eio.Time.now clock in
Fiber.both
(fun () ->
if use_domains then (
Eio.Domain_manager.run domain_mgr @@ fun () ->
run_sender ~n_iters stream
) else (
run_sender ~n_iters stream
) )
)
(fun () ->
for i = 1 to n_iters do
let j = Eio.Stream.take stream in
assert (i = j)
done done
(* Read [n_iters] values from [stream] and add them to [total] (at the end). *)
let run_recv ~n_iters ~total stream =
Switch.run @@ fun sw ->
Fiber.fork_daemon ~sw spin;
let rec aux acc = function
| 0 -> acc
| i -> aux (acc + Eio.Stream.take stream) (i - 1) in
ignore (Atomic.fetch_and_add total (aux 0 n_iters) : int)
(* Run the tests using [n_sender_domains] additional domains to send (0 to send
and receive in a single domain). When [n_sender_domains > 0], we also use
that many receiver domains. *)
let run_bench ~domain_mgr ~clock ~n_send_domains ~n_iters ~capacity =
let stream = Eio.Stream.create capacity in
let total = Atomic.make 0 in (* Total received (sanity check at the end) *)
let n_senders = max 1 n_send_domains in
let n_iters_total = (* Total number of items to be sent through [stream] *)
n_iters * n_sender_fibers * n_senders
in
Gc.full_major ();
let t0 = Eio.Time.now clock in
Switch.run (fun sw ->
let run_sender () = run_sender ~n_fibers:n_sender_fibers ~n_iters stream in
if n_send_domains > 0 then (
for _ = 1 to n_send_domains do
Fiber.fork ~sw (fun () -> Eio.Domain_manager.run domain_mgr run_sender)
done
) else (
Fiber.fork ~sw run_sender
);
let run_recv () = run_recv ~n_iters:(n_iters * n_sender_fibers) ~total stream in
for _ = 1 to n_senders - 1 do
Fiber.fork ~sw @@ fun () ->
Eio.Domain_manager.run domain_mgr run_recv
done;
Fiber.fork ~sw run_recv
); );
let t1 = Eio.Time.now clock in let t1 = Eio.Time.now clock in
let total = Atomic.get total in
let expected_total = n_senders * n_sender_fibers * (n_iters * (1 + n_iters) / 2) in
assert (total = expected_total);
let time_total = t1 -. t0 in let time_total = t1 -. t0 in
let time_per_iter = time_total /. float n_iters in let time_per_iter = time_total /. float n_iters_total in
let _minor1, prom1, _major1 = Gc.counters () in Metric.create
let prom = prom1 -. prom0 in (Printf.sprintf "sender-domains:%d iterations:%d capacity:%d" n_send_domains n_iters capacity)
Printf.printf "%11b, %8d, %8d, %7.2f, %13.4f\n%!" use_domains n_iters capacity (1e9 *. time_per_iter) (prom /. float n_iters) (`Float (1e9 *. time_per_iter)) "ns"
"Time to transmit one item over the stream"
let main ~domain_mgr ~clock = let main ~domain_mgr ~clock =
Printf.printf "use_domains, n_iters, capacity, ns/iter, promoted/iter\n%!"; [0, 100_000;
[false, 10_000_000; 1, 100_000;
true, 1_000_000] 2, 100_000;
|> List.iter (fun (use_domains, n_iters) -> 4, 100_000;
[0; 1; 10; 100; 1000] |> List.iter (fun capacity -> ]
run_bench ~domain_mgr ~clock ~use_domains ~n_iters ~capacity |> List.concat_map (fun (n_send_domains, n_iters) ->
[0; 1; 100] |> List.map (fun capacity ->
run_bench ~domain_mgr ~clock ~n_send_domains ~n_iters ~capacity
) )
) )
let () = let run env =
Eio_main.run @@ fun env ->
main main
~domain_mgr:(Eio.Stdenv.domain_mgr env) ~domain_mgr:(Eio.Stdenv.domain_mgr env)
~clock:(Eio.Stdenv.clock env) ~clock:(Eio.Stdenv.clock env)

76
bench/bench_systhread.ml Normal file
View File

@ -0,0 +1,76 @@
(* Measure the overhead of [Eio_unix.run_in_systhread]. *)
open Eio.Std
let n_iters = 1000
let do_syscall () = ignore (Unix.getuid () : int)
let work () =
for _ = 1 to n_iters do
Eio_unix.run_in_systhread do_syscall
done
(* Return the average time for one call to [getuid]. *)
let run_domain ~fibers =
let t0 = Unix.gettimeofday () in
Switch.run ~name:"run_domain" (fun sw ->
for _ = 1 to fibers do
Fiber.fork ~sw work
done
);
let t1 = Unix.gettimeofday () in
(t1 -. t0) /. float n_iters
let time ~domain_mgr ~baseline ~domains ~fibers =
let overhead t = t /. baseline in
let name = Printf.sprintf "domains:%d fibers:%d" domains fibers in
(* Work-around for https://github.com/ocaml/ocaml/issues/12948 *)
let main_done, set_main_done = Promise.create () in
Switch.run ~name @@ fun sw ->
let times =
List.init (domains - 1) (fun _ ->
Fiber.fork_promise ~sw (fun () ->
Eio.Domain_manager.run domain_mgr (fun () ->
let r = run_domain ~fibers in
Promise.await main_done;
r
)
)
)
in
let my_time = run_domain ~fibers in
Promise.resolve set_main_done (); (* Allow Domain.join to be called *)
let times =
my_time :: List.map Promise.await_exn times
|> List.map (fun t -> t *. 1e6)
in
traceln "%s" name;
times |> List.iteri (fun i t ->
traceln "%d: %.2f us (%.1f times slower)" i t (overhead t)
);
let avg = (List.fold_left (+.) 0. times) /. float domains in
Metric.create name (`Float avg) "us" name
let run env =
let domain_mgr = env#domain_mgr in
let baseline =
Eio.Private.Trace.with_span "baseline" @@ fun () ->
let t0 = Unix.gettimeofday () in
for _ = 1 to n_iters do
do_syscall ()
done;
let t1 = Unix.gettimeofday () in
((t1 -. t0) /. float n_iters) *. 1e6
in
traceln "baseline (no systhreads): %.2f us" baseline;
let results =
[
time ~domains:1 ~fibers:1;
time ~domains:1 ~fibers:2;
time ~domains:1 ~fibers:4;
time ~domains:4 ~fibers:1;
]
|> List.map (fun f -> f ~domain_mgr ~baseline)
in
Metric.create "blocking" (`Float baseline) "us" "baseline" :: results

View File

@ -1,13 +1,11 @@
open Eio.Std open Eio.Std
let n_fibers = [1; 2; 3; 4; 5; 10; 20; 30; 40; 50; 100; 500; 1000; 10000] let n_fibers = [1; 2; (* 3; 4; 5; 10; 20; 30; 40; 50; *) 100; 500; 1000; 10000]
let main ~clock = let main ~clock =
Printf.printf "n_fibers, ns/iter, promoted/iter\n%!"; n_fibers |> List.map (fun n_fibers ->
n_fibers |> List.iter (fun n_fibers ->
let n_iters = 1000000 / n_fibers in let n_iters = 1000000 / n_fibers in
Gc.full_major (); Gc.full_major ();
let _minor0, prom0, _major0 = Gc.counters () in
let t0 = Eio.Time.now clock in let t0 = Eio.Time.now clock in
Switch.run (fun sw -> Switch.run (fun sw ->
for _ = 1 to n_fibers do for _ = 1 to n_fibers do
@ -22,11 +20,10 @@ let main ~clock =
let time_total = t1 -. t0 in let time_total = t1 -. t0 in
let n_total = n_fibers * n_iters in let n_total = n_fibers * n_iters in
let time_per_iter = time_total /. float n_total in let time_per_iter = time_total /. float n_total in
let _minor1, prom1, _major1 = Gc.counters () in Metric.create
let prom = prom1 -. prom0 in (Printf.sprintf "fibers:%d" n_fibers)
Printf.printf "%8d, % 7.2f, % 13.4f\n%!" n_fibers (1e9 *. time_per_iter) (prom /. float n_total) (`Float (1e9 *. time_per_iter)) "ns" "Time to yield"
) )
let () = let run env =
Eio_main.run @@ fun env ->
main ~clock:(Eio.Stdenv.clock env) main ~clock:(Eio.Stdenv.clock env)

View File

@ -1,3 +1,8 @@
(executables ; This should be an executable, but dune won't let us associate non-installed executables
(names bench_stream bench_promise bench_semaphore bench_yield bench_cancel) ; to packages, so we use this work-around.
(libraries eio_main)) (test
(name main)
(package eio_main)
(deps ./main.exe)
(action (progn)) ; Don't run as a test
(libraries eio_main yojson))

63
bench/main.ml Normal file
View File

@ -0,0 +1,63 @@
open Eio.Std
let benchmarks = [
"Promise", Bench_promise.run;
"Cancel", Bench_cancel.run;
"Buf_read", Bench_buf_read.run;
"Condition", Bench_condition.run;
"Fiber.yield", Bench_yield.run;
"Mutex", Bench_mutex.run;
"Semaphore", Bench_semaphore.run;
"Stream", Bench_stream.run;
"HTTP", Bench_http.run;
"Eio_unix.Fd", Bench_fd.run;
"File.stat", Bench_fstat.run;
"Path.stat", Bench_stat.run;
"Flow.copy", Bench_copy.run;
"Eio_unix.run_in_systhread", Bench_systhread.run;
]
let usage_error () =
let names = List.map fst benchmarks in
Fmt.epr "Usage: main.exe [%a]@." Fmt.(list ~sep:(any " | ") string) names;
exit 1
let () =
Eio_main.run @@ fun env ->
traceln "Using %s backend" env#backend_id;
let benchmarks =
match Array.to_list Sys.argv with
| [_] -> benchmarks
| [_; name] ->
begin match List.assoc_opt name benchmarks with
| Some run -> [name, run]
| None ->
Fmt.epr "Unknown benchmark %S@." name;
usage_error ()
end
| _ -> usage_error ()
in
let run (name, fn) =
traceln "Running %s..." name;
let metrics = fn env in
`Assoc [
"name", `String name;
"metrics", `List metrics;
]
in
(* The benchmark machine runs an old Docker that blocks pidfd_open *)
(* let uname = Eio.Process.parse_out env#process_mgr Eio.Buf_read.take_all ["uname"; "-a"] in *)
let uname =
let ch = Unix.open_process_in "uname -a" in
let x = input_line ch in
close_in ch;
x
in
Fmt.pr "%a@." (Yojson.Safe.pretty_print ~std:true) @@ `Assoc [
"config", `Assoc [
"uname", `String uname;
"backend", `String env#backend_id;
"recommended_domain_count", `Int (Domain.recommended_domain_count ());
];
"results", `List (List.map run benchmarks);
]

7
bench/metric.ml Normal file
View File

@ -0,0 +1,7 @@
let create name value units description : Yojson.Safe.t =
`Assoc [
"name", `String name;
"value", (value :> Yojson.Safe.t);
"units", `String units;
"description", `String description;
]

View File

@ -1,4 +1,5 @@
(mdx (mdx
(package eio_main) (package eio_main)
(packages eio_main) (deps (package eio_main) (env_var "EIO_BACKEND"))
(files multicore.md eio_null.md)) (enabled_if (<> %{os_type} "Win32"))
(files multicore.md))

View File

@ -1,104 +0,0 @@
```ocaml
# #require "eio.utils";;
```
# A dummy Eio backend with no actual effects
This is very inefficient and not thread-safe, but it demonstrates the idea.
A real backend would typically pass `main` some way to interact with it, like the other backends do.
```ocaml
open Eio.Std
(* An Eio backend with no actual IO *)
module Eio_null = struct
module Fiber_context = Eio.Private.Fiber_context
module Effect = Eio.Private.Effect (* For compatibility with 4.12+domains *)
(* The scheduler could just return [unit], but this is clearer. *)
type exit = Exit_scheduler
type t = {
(* Suspended fibers waiting to run again.
A real system would probably use [Eio_utils.Lf_queue]. *)
mutable run_q : (unit -> exit) list;
}
(* Resume the next runnable fiber, if any. *)
let schedule t : exit =
match t.run_q with
| f :: fs -> t.run_q <- fs; f ()
| [] -> Exit_scheduler (* Finished (or deadlocked) *)
(* Run [main] in an Eio main loop. *)
let run main =
let t = { run_q = [] } in
let rec fork ~new_fiber:fiber fn =
(* Create a new fiber and run [fn] in it. *)
Effect.Deep.match_with fn ()
{ retc = (fun () -> Fiber_context.destroy fiber; schedule t);
exnc = (fun ex -> Fiber_context.destroy fiber; raise ex);
effc = fun (type a) (e : a Effect.t) : ((a, exit) Effect.Deep.continuation -> exit) option ->
match e with
| Eio.Private.Effects.Suspend f -> Some (fun k ->
(* Ask [f] to register whatever callbacks are needed to resume the fiber.
e.g. it might register a callback with a promise, for when that's resolved. *)
f fiber (function
(* The fiber is ready to run again. Add it to the queue. *)
| Ok v -> t.run_q <- t.run_q @ [fun () -> Effect.Deep.continue k v]
| Error ex -> t.run_q <- t.run_q @ [fun () -> Effect.Deep.discontinue k ex]
);
(* Switch to the next runnable fiber while this one's blocked. *)
schedule t
)
| Eio.Private.Effects.Fork (new_fiber, f) -> Some (fun k ->
(* Arrange for the forking fiber to run immediately after the new one. *)
t.run_q <- Effect.Deep.continue k :: t.run_q;
(* Create and run the new fiber (using fiber context [new_fiber]). *)
fork ~new_fiber f
)
| Eio.Private.Effects.Get_context -> Some (fun k ->
Effect.Deep.continue k fiber
)
| Eio.Private.Effects.Trace -> Some (fun k ->
Effect.Deep.continue k Eio_utils.Trace.default_traceln
)
| _ -> None
}
in
let new_fiber = Fiber_context.make_root () in
let Exit_scheduler = fork ~new_fiber main in
()
end
```
It supports forking, tracing, suspending and cancellation:
```ocaml
# Eio_null.run @@ fun () ->
let s = Eio.Stream.create 1 in
try
Fiber.both
(fun () ->
for x = 1 to 3 do
traceln "Sending %d" x;
Eio.Stream.add s x
done;
raise Exit
)
(fun () ->
while true do
traceln "Got %d" (Eio.Stream.take s)
done
)
with Exit ->
traceln "Finished!";;
+Sending 1
+Sending 2
+Got 1
+Got 2
+Sending 3
+Got 3
+Finished!
- : unit = ()
```

View File

@ -11,14 +11,15 @@
* [The OCaml Memory Model](#the-ocaml-memory-model) * [The OCaml Memory Model](#the-ocaml-memory-model)
* [Atomic Locations](#atomic-locations) * [Atomic Locations](#atomic-locations)
* [Initialisation](#initialisation) * [Initialisation](#initialisation)
* [Guidelines](#guidelines) * [Safety Guidelines](#safety-guidelines)
* [Performance Guidelines](#performance-guidelines)
* [Further Reading](#further-reading) * [Further Reading](#further-reading)
<!-- vim-markdown-toc --> <!-- vim-markdown-toc -->
## Introduction ## Introduction
OCaml 5.00 adds support for using multiple CPU cores in a single OCaml process. OCaml 5.0 adds support for using multiple CPU cores in a single OCaml process.
An OCaml process is made up of one or more *domains*, and An OCaml process is made up of one or more *domains*, and
the operating system can run each domain on a different core, so that they run in parallel. the operating system can run each domain on a different core, so that they run in parallel.
This can make programs run much faster, but also introduces new ways for programs to go wrong. This can make programs run much faster, but also introduces new ways for programs to go wrong.
@ -41,7 +42,7 @@ Before we start, we'll define a wrapper around `Eio_main.run` for the examples b
Eio_main.run @@ fun env -> Eio_main.run @@ fun env ->
let domain_mgr = Eio.Stdenv.domain_mgr env in let domain_mgr = Eio.Stdenv.domain_mgr env in
fn (Eio.Domain_manager.run domain_mgr);; fn (Eio.Domain_manager.run domain_mgr);;
val run : (((unit -> 'a) -> 'a) -> unit) -> unit = <fun> val run : (((unit -> 'a) -> 'a) -> 'b) -> 'b = <fun>
``` ```
## Problems with Multicore Programming ## Problems with Multicore Programming
@ -446,7 +447,7 @@ So it will always see a correct list:
- : unit = () - : unit = ()
``` ```
## Guidelines ## Safety Guidelines
It's important to understand the above to avoid writing incorrect code, It's important to understand the above to avoid writing incorrect code,
but there are several general principles that avoid most problems: but there are several general principles that avoid most problems:
@ -502,6 +503,28 @@ Finally, note that OCaml remains type-safe even with multiple domains.
For example, accessing a `Queue` in parallel from multiple domains may result in a corrupted queue, For example, accessing a `Queue` in parallel from multiple domains may result in a corrupted queue,
but it won't cause a segfault. but it won't cause a segfault.
## Performance Guidelines
The following recommendations will help you extract as much performance as possible from your hardware:
- There's a certain overhead associated with placing execution onto another domain,
but that overhead will be paid off quickly if your job takes at least a few milliseconds to complete.
Jobs that complete under 2-5ms may not be worth running on a separate domain.
- Similarly, jobs that are 100% I/O-bound may not be worth running on a separate domain.
The small initial overhead is simply never recouped.
- If your program never hits 100% CPU usage, it's unlikely that parallelizing it will improve performance.
- Try to avoid reading or writing to memory that's modified by other domains after the start of your job.
Ideally, your jobs shouldn't need to interact with other domains' "working data".
Aim to make your jobs as independent as possible.
If unavoidable, the [Saturn](https://github.com/ocaml-multicore/saturn) library offers a collection of efficient threadsafe data structures.
- It's often easier to design code to be multithreading friendly from the start
(by making longer, independent jobs) than by refactoring existing code.
- There's a cost associated with creating a domain, so try to use the same domains for longer periods of time.
`Eio.Executor_pool` takes care of this automatically.
- Obviously, reuse the same executor pool whenever possible! Don't recreate it over and over.
- Having a large number of domains active at the same time imposes additional overhead on
both the OS scheduler and the OCaml runtime, even if those domains are idle.
## Further Reading ## Further Reading
- [OCaml Memory Model][] describes the full details of the memory model. - [OCaml Memory Model][] describes the full details of the memory model.

View File

@ -1,40 +1,34 @@
#require "eio_main";; #require "eio_main";;
#require "eio.mock";;
module Eio_main = struct module Eio_main = struct
open Eio.Std open Eio.Std
let now = ref 1623940778.27033591 module Fake_clock = struct
type time = float
let fake_clock real_clock = object (_ : #Eio.Time.clock) type t = unit
method now = !now let sleep_until () _time = failwith "No sleeping in tests!"
method sleep_until time = let now _ = 1623940778.27033591
(* The fake times are all in the past, so we just ask to wait until the
fake time is due and it will happen immediately. If we wait for
multiple times, they'll get woken in the right order. At the moment,
the scheduler only checks for expired timers when the run-queue is
empty, so this is a convenient way to wait for the system to be idle.
Will need revising if we make the scheduler fair at some point. *)
Eio.Time.sleep_until real_clock time;
now := max !now time
end end
(* To avoid non-deterministic output, we run the examples a single domain. *) let fake_clock =
let fake_domain_mgr = object (_ : #Eio.Domain_manager.t) let handler = Eio.Time.Pi.clock (module Fake_clock) in
method run fn = fn () Eio.Resource.T ((), handler)
method run_raw fn = fn ()
end
(* https://github.com/ocaml/ocaml/issues/10324 *)
let dontcrash = Sys.opaque_identity
let run fn = let run fn =
(* To avoid non-deterministic output, we run the examples a single domain. *)
let fake_domain_mgr = Eio_mock.Domain_manager.create () in
Eio_main.run @@ fun env -> Eio_main.run @@ fun env ->
fn @@ object fn @@ object
method net = dontcrash env#net method net = env#net
method stdin = dontcrash env#stdin method stdin = env#stdin
method stdout = dontcrash env#stdout method stdout = env#stdout
method cwd = dontcrash env#cwd method stderr = env#stderr
method cwd = env#cwd
method process_mgr = env#process_mgr
method domain_mgr = fake_domain_mgr method domain_mgr = fake_domain_mgr
method clock = fake_clock env#clock method clock = fake_clock
end end
end end
let parse_config (flow : _ Eio.Flow.source) = ignore

View File

@ -69,7 +69,7 @@ For example, there are many ways to provide a stream of bytes (from a file, TCP
Often this choice is determined by the user at runtime, for example by providing a URL giving the scheme to use. Often this choice is determined by the user at runtime, for example by providing a URL giving the scheme to use.
We may even need to choose a completely different Eio backend at runtime. We may even need to choose a completely different Eio backend at runtime.
For example `Eio_main.run` will use the io_uring backend if the Linux kernel is new enough, For example `Eio_main.run` will use the io_uring backend if the Linux kernel is new enough,
but fall back to `Eio_luv` if not. but fall back to `Eio_posix` if not.
For these reasons, Eio needs to use dynamic dispatch. For these reasons, Eio needs to use dynamic dispatch.
A resource whose implementation isn't known until runtime can be represented in many ways, including: A resource whose implementation isn't known until runtime can be represented in many ways, including:
@ -125,7 +125,7 @@ For dynamic dispatch with subtyping, objects seem to be the best choice:
An object uses a single block to store the object's fields and a pointer to the shared method table. An object uses a single block to store the object's fields and a pointer to the shared method table.
- First-class modules and GADTs are an advanced feature of the language. - First-class modules and GADTs are an advanced feature of the language.
The new users we hope to attract to OCaml 5.00 are likely to be familiar with objects already. The new users we hope to attract to OCaml 5.0 are likely to be familiar with objects already.
- It is possible to provide base classes with default implementations of some methods. - It is possible to provide base classes with default implementations of some methods.
This can allow adding new operations to the API in future without breaking existing providers. This can allow adding new operations to the API in future without breaking existing providers.
@ -133,21 +133,35 @@ For dynamic dispatch with subtyping, objects seem to be the best choice:
In general, simulating objects using other features of the language leads to worse performance In general, simulating objects using other features of the language leads to worse performance
and worse ergonomics than using the language's built-in support. and worse ergonomics than using the language's built-in support.
In Eio, we split the provider and consumer APIs: However, in order for Eio to be widely accepted in the OCaml community,
we no longer use of objects and instead use a pair of a value and a function for looking up interfaces.
There is a problem here, because each interface has a different type,
so the function's return type depends on its input (the interface ID).
This requires using a GADT. However, GADT's don't support sub-typing.
To get around this, we use an extensible GADT to get the correct typing
(but which will raise an exception if the interface isn't supported),
and then wrap this with a polymorphic variant phantom type to help ensure
it is used correctly.
- To *provide* a flow, you implement an object type. This system gives the same performance as using objects and without requiring allocation.
- To *use* a flow, you call a function (e.g. `Flow.close`). However, care is needed when defining new interfaces,
since the compiler can't check that the resource really implements all the interfaces its phantom type suggests.
The functions mostly just call the corresponding method on the object. ## Results vs Exceptions
If you call object methods directly in OCaml then you tend to get poor compiler error messages.
This is because OCaml can only refer to the object types by listing the methods you seem to want to use.
Using functions avoids this, because the function signature specifies the type of its argument,
allowing type inference to work as for non-object code.
In this way, users of Eio can be largely unaware that objects are being used at all.
The function wrappers can also provide extra checks that the API is being followed correctly, The OCaml standard library uses exceptions to report errors in most cases.
such as asserting that a read does not return 0 bytes, Many libraries instead use the `result` type, which has the advantage of tracking the possible errors in the type system.
or add extra convenience functions without forcing every implementor to add them too. However, using `result` is slower, as it requires more allocations, and explicit code to propagate errors.
Note that the use of objects in Eio is not motivated by the use of the "Object Capabilities" security model. As part of the effects work, OCaml is expected to gain a [typed effects][] extension to the type system,
Despite the name, that is not specific to objects at all. allowing it to track both effects and exceptions statically.
In anticipation of this, the Eio library prefers to use exceptions in most cases,
reserving the use of `result` for cases where the caller is likely to want to handle the problem immediately
rather than propagate it.
In additional, while result types work well
for functions with a small number of known errors which can be handled at the call-site,
they work poorly for IO errors where there are typically a large and unknown set of possible errors
(depending on the backend).
[typed effects]: https://www.janestreet.com/tech-talks/effective-programming/

View File

@ -1,223 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="580pt" height="132pt" viewBox="0 0 580 132" version="1.1">
<defs>
<g>
<symbol overflow="visible" id="glyph0-0">
<path style="stroke:none;" d="M 0.59375 2.125 L 0.59375 -8.46875 L 6.59375 -8.46875 L 6.59375 2.125 Z M 1.265625 1.453125 L 5.9375 1.453125 L 5.9375 -7.78125 L 1.265625 -7.78125 Z M 1.265625 1.453125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-1">
<path style="stroke:none;" d="M 1.171875 -8.75 L 6.703125 -8.75 L 6.703125 -7.75 L 2.359375 -7.75 L 2.359375 -5.15625 L 6.53125 -5.15625 L 6.53125 -4.171875 L 2.359375 -4.171875 L 2.359375 -1 L 6.8125 -1 L 6.8125 0 L 1.171875 0 Z M 1.171875 -8.75 "/>
</symbol>
<symbol overflow="visible" id="glyph0-2">
<path style="stroke:none;" d="M 4.109375 -3.296875 C 3.242188 -3.296875 2.640625 -3.195312 2.296875 -3 C 1.960938 -2.800781 1.796875 -2.460938 1.796875 -1.984375 C 1.796875 -1.597656 1.921875 -1.289062 2.171875 -1.0625 C 2.429688 -0.84375 2.773438 -0.734375 3.203125 -0.734375 C 3.804688 -0.734375 4.285156 -0.941406 4.640625 -1.359375 C 5.003906 -1.785156 5.1875 -2.351562 5.1875 -3.0625 L 5.1875 -3.296875 Z M 6.265625 -3.75 L 6.265625 0 L 5.1875 0 L 5.1875 -1 C 4.9375 -0.601562 4.628906 -0.304688 4.265625 -0.109375 C 3.898438 0.078125 3.453125 0.171875 2.921875 0.171875 C 2.242188 0.171875 1.707031 -0.015625 1.3125 -0.390625 C 0.914062 -0.773438 0.71875 -1.28125 0.71875 -1.90625 C 0.71875 -2.644531 0.960938 -3.203125 1.453125 -3.578125 C 1.953125 -3.953125 2.691406 -4.140625 3.671875 -4.140625 L 5.1875 -4.140625 L 5.1875 -4.25 C 5.1875 -4.75 5.019531 -5.132812 4.6875 -5.40625 C 4.363281 -5.675781 3.910156 -5.8125 3.328125 -5.8125 C 2.953125 -5.8125 2.582031 -5.765625 2.21875 -5.671875 C 1.863281 -5.578125 1.523438 -5.441406 1.203125 -5.265625 L 1.203125 -6.265625 C 1.597656 -6.421875 1.976562 -6.535156 2.34375 -6.609375 C 2.71875 -6.679688 3.082031 -6.71875 3.4375 -6.71875 C 4.382812 -6.71875 5.09375 -6.472656 5.5625 -5.984375 C 6.03125 -5.492188 6.265625 -4.75 6.265625 -3.75 Z M 6.265625 -3.75 "/>
</symbol>
<symbol overflow="visible" id="glyph0-3">
<path style="stroke:none;" d="M 5.859375 -6.3125 L 5.859375 -5.296875 C 5.546875 -5.472656 5.238281 -5.601562 4.9375 -5.6875 C 4.632812 -5.769531 4.328125 -5.8125 4.015625 -5.8125 C 3.304688 -5.8125 2.757812 -5.585938 2.375 -5.140625 C 1.988281 -4.703125 1.796875 -4.082031 1.796875 -3.28125 C 1.796875 -2.476562 1.988281 -1.851562 2.375 -1.40625 C 2.757812 -0.96875 3.304688 -0.75 4.015625 -0.75 C 4.328125 -0.75 4.632812 -0.789062 4.9375 -0.875 C 5.238281 -0.957031 5.546875 -1.082031 5.859375 -1.25 L 5.859375 -0.25 C 5.554688 -0.113281 5.242188 -0.0078125 4.921875 0.0625 C 4.597656 0.132812 4.253906 0.171875 3.890625 0.171875 C 2.898438 0.171875 2.113281 -0.132812 1.53125 -0.75 C 0.945312 -1.375 0.65625 -2.21875 0.65625 -3.28125 C 0.65625 -4.34375 0.945312 -5.179688 1.53125 -5.796875 C 2.125 -6.410156 2.9375 -6.71875 3.96875 -6.71875 C 4.289062 -6.71875 4.609375 -6.679688 4.921875 -6.609375 C 5.242188 -6.546875 5.554688 -6.445312 5.859375 -6.3125 Z M 5.859375 -6.3125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-4">
<path style="stroke:none;" d="M 6.59375 -3.96875 L 6.59375 0 L 5.515625 0 L 5.515625 -3.921875 C 5.515625 -4.546875 5.390625 -5.007812 5.140625 -5.3125 C 4.898438 -5.625 4.539062 -5.78125 4.0625 -5.78125 C 3.476562 -5.78125 3.015625 -5.59375 2.671875 -5.21875 C 2.335938 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -9.125 L 2.171875 -9.125 L 2.171875 -5.546875 C 2.429688 -5.941406 2.734375 -6.234375 3.078125 -6.421875 C 3.429688 -6.617188 3.835938 -6.71875 4.296875 -6.71875 C 5.046875 -6.71875 5.613281 -6.484375 6 -6.015625 C 6.394531 -5.554688 6.59375 -4.875 6.59375 -3.96875 Z M 6.59375 -3.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-5">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph0-6">
<path style="stroke:none;" d="M 5.453125 -3.359375 C 5.453125 -4.140625 5.289062 -4.742188 4.96875 -5.171875 C 4.644531 -5.597656 4.191406 -5.8125 3.609375 -5.8125 C 3.035156 -5.8125 2.585938 -5.597656 2.265625 -5.171875 C 1.941406 -4.742188 1.78125 -4.140625 1.78125 -3.359375 C 1.78125 -2.578125 1.941406 -1.972656 2.265625 -1.546875 C 2.585938 -1.117188 3.035156 -0.90625 3.609375 -0.90625 C 4.191406 -0.90625 4.644531 -1.117188 4.96875 -1.546875 C 5.289062 -1.972656 5.453125 -2.578125 5.453125 -3.359375 Z M 6.53125 -0.8125 C 6.53125 0.300781 6.28125 1.128906 5.78125 1.671875 C 5.289062 2.222656 4.53125 2.5 3.5 2.5 C 3.125 2.5 2.765625 2.46875 2.421875 2.40625 C 2.085938 2.351562 1.765625 2.269531 1.453125 2.15625 L 1.453125 1.109375 C 1.765625 1.273438 2.078125 1.398438 2.390625 1.484375 C 2.703125 1.566406 3.015625 1.609375 3.328125 1.609375 C 4.035156 1.609375 4.566406 1.421875 4.921875 1.046875 C 5.273438 0.679688 5.453125 0.125 5.453125 -0.625 L 5.453125 -1.15625 C 5.222656 -0.769531 4.9375 -0.476562 4.59375 -0.28125 C 4.25 -0.09375 3.832031 0 3.34375 0 C 2.539062 0 1.890625 -0.304688 1.390625 -0.921875 C 0.898438 -1.535156 0.65625 -2.347656 0.65625 -3.359375 C 0.65625 -4.367188 0.898438 -5.179688 1.390625 -5.796875 C 1.890625 -6.410156 2.539062 -6.71875 3.34375 -6.71875 C 3.832031 -6.71875 4.25 -6.617188 4.59375 -6.421875 C 4.9375 -6.234375 5.222656 -5.945312 5.453125 -5.5625 L 5.453125 -6.5625 L 6.53125 -6.5625 Z M 6.53125 -0.8125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-7">
<path style="stroke:none;" d="M 4.9375 -5.5625 C 4.8125 -5.625 4.675781 -5.671875 4.53125 -5.703125 C 4.394531 -5.742188 4.238281 -5.765625 4.0625 -5.765625 C 3.457031 -5.765625 2.988281 -5.566406 2.65625 -5.171875 C 2.332031 -4.773438 2.171875 -4.203125 2.171875 -3.453125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.398438 -5.941406 2.695312 -6.234375 3.0625 -6.421875 C 3.425781 -6.617188 3.867188 -6.71875 4.390625 -6.71875 C 4.460938 -6.71875 4.539062 -6.710938 4.625 -6.703125 C 4.71875 -6.691406 4.816406 -6.675781 4.921875 -6.65625 Z M 4.9375 -5.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-8">
<path style="stroke:none;" d="M 1.125 -6.5625 L 2.203125 -6.5625 L 2.203125 0 L 1.125 0 Z M 1.125 -9.125 L 2.203125 -9.125 L 2.203125 -7.75 L 1.125 -7.75 Z M 1.125 -9.125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-9">
<path style="stroke:none;" d="M 5.453125 -5.5625 L 5.453125 -9.125 L 6.53125 -9.125 L 6.53125 0 L 5.453125 0 L 5.453125 -0.984375 C 5.222656 -0.597656 4.9375 -0.304688 4.59375 -0.109375 C 4.25 0.078125 3.832031 0.171875 3.34375 0.171875 C 2.550781 0.171875 1.90625 -0.144531 1.40625 -0.78125 C 0.90625 -1.414062 0.65625 -2.25 0.65625 -3.28125 C 0.65625 -4.3125 0.90625 -5.140625 1.40625 -5.765625 C 1.90625 -6.398438 2.550781 -6.71875 3.34375 -6.71875 C 3.832031 -6.71875 4.25 -6.625 4.59375 -6.4375 C 4.9375 -6.25 5.222656 -5.957031 5.453125 -5.5625 Z M 1.78125 -3.28125 C 1.78125 -2.488281 1.941406 -1.863281 2.265625 -1.40625 C 2.585938 -0.957031 3.035156 -0.734375 3.609375 -0.734375 C 4.179688 -0.734375 4.628906 -0.957031 4.953125 -1.40625 C 5.285156 -1.863281 5.453125 -2.488281 5.453125 -3.28125 C 5.453125 -4.070312 5.285156 -4.691406 4.953125 -5.140625 C 4.628906 -5.585938 4.179688 -5.8125 3.609375 -5.8125 C 3.035156 -5.8125 2.585938 -5.585938 2.265625 -5.140625 C 1.941406 -4.691406 1.78125 -4.070312 1.78125 -3.28125 Z M 1.78125 -3.28125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-10">
<path style="stroke:none;" d="M 0.359375 -6.5625 L 1.5 -6.5625 L 3.546875 -1.0625 L 5.609375 -6.5625 L 6.75 -6.5625 L 4.28125 0 L 2.8125 0 Z M 0.359375 -6.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-11">
<path style="stroke:none;" d="M 5.3125 -6.375 L 5.3125 -5.34375 C 5.007812 -5.5 4.691406 -5.613281 4.359375 -5.6875 C 4.035156 -5.769531 3.695312 -5.8125 3.34375 -5.8125 C 2.8125 -5.8125 2.410156 -5.726562 2.140625 -5.5625 C 1.867188 -5.40625 1.734375 -5.160156 1.734375 -4.828125 C 1.734375 -4.578125 1.828125 -4.378906 2.015625 -4.234375 C 2.210938 -4.097656 2.601562 -3.96875 3.1875 -3.84375 L 3.546875 -3.75 C 4.316406 -3.59375 4.863281 -3.363281 5.1875 -3.0625 C 5.507812 -2.757812 5.671875 -2.34375 5.671875 -1.8125 C 5.671875 -1.195312 5.425781 -0.710938 4.9375 -0.359375 C 4.457031 -0.00390625 3.796875 0.171875 2.953125 0.171875 C 2.597656 0.171875 2.226562 0.132812 1.84375 0.0625 C 1.46875 0 1.070312 -0.0976562 0.65625 -0.234375 L 0.65625 -1.359375 C 1.050781 -1.148438 1.441406 -0.992188 1.828125 -0.890625 C 2.210938 -0.785156 2.597656 -0.734375 2.984375 -0.734375 C 3.484375 -0.734375 3.867188 -0.816406 4.140625 -0.984375 C 4.421875 -1.160156 4.5625 -1.410156 4.5625 -1.734375 C 4.5625 -2.023438 4.460938 -2.25 4.265625 -2.40625 C 4.066406 -2.5625 3.632812 -2.710938 2.96875 -2.859375 L 2.59375 -2.9375 C 1.925781 -3.082031 1.441406 -3.300781 1.140625 -3.59375 C 0.847656 -3.882812 0.703125 -4.28125 0.703125 -4.78125 C 0.703125 -5.40625 0.921875 -5.882812 1.359375 -6.21875 C 1.796875 -6.550781 2.414062 -6.71875 3.21875 -6.71875 C 3.613281 -6.71875 3.988281 -6.6875 4.34375 -6.625 C 4.695312 -6.570312 5.019531 -6.488281 5.3125 -6.375 Z M 5.3125 -6.375 "/>
</symbol>
<symbol overflow="visible" id="glyph0-12">
<path style="stroke:none;" d="M 3.671875 -5.8125 C 3.097656 -5.8125 2.640625 -5.582031 2.296875 -5.125 C 1.960938 -4.675781 1.796875 -4.0625 1.796875 -3.28125 C 1.796875 -2.488281 1.960938 -1.867188 2.296875 -1.421875 C 2.628906 -0.972656 3.085938 -0.75 3.671875 -0.75 C 4.242188 -0.75 4.695312 -0.972656 5.03125 -1.421875 C 5.375 -1.878906 5.546875 -2.5 5.546875 -3.28125 C 5.546875 -4.050781 5.375 -4.664062 5.03125 -5.125 C 4.695312 -5.582031 4.242188 -5.8125 3.671875 -5.8125 Z M 3.671875 -6.71875 C 4.609375 -6.71875 5.34375 -6.410156 5.875 -5.796875 C 6.414062 -5.191406 6.6875 -4.351562 6.6875 -3.28125 C 6.6875 -2.207031 6.414062 -1.363281 5.875 -0.75 C 5.34375 -0.132812 4.609375 0.171875 3.671875 0.171875 C 2.734375 0.171875 1.992188 -0.132812 1.453125 -0.75 C 0.921875 -1.363281 0.65625 -2.207031 0.65625 -3.28125 C 0.65625 -4.351562 0.921875 -5.191406 1.453125 -5.796875 C 1.992188 -6.410156 2.734375 -6.71875 3.671875 -6.71875 Z M 3.671875 -6.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-13">
<path style="stroke:none;" d="M 6.59375 -3.96875 L 6.59375 0 L 5.515625 0 L 5.515625 -3.921875 C 5.515625 -4.546875 5.390625 -5.007812 5.140625 -5.3125 C 4.898438 -5.625 4.539062 -5.78125 4.0625 -5.78125 C 3.476562 -5.78125 3.015625 -5.59375 2.671875 -5.21875 C 2.335938 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.429688 -5.941406 2.734375 -6.234375 3.078125 -6.421875 C 3.429688 -6.617188 3.835938 -6.71875 4.296875 -6.71875 C 5.046875 -6.71875 5.613281 -6.484375 6 -6.015625 C 6.394531 -5.554688 6.59375 -4.875 6.59375 -3.96875 Z M 6.59375 -3.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-14">
<path style="stroke:none;" d="M 1.40625 -1.484375 L 2.640625 -1.484375 L 2.640625 0 L 1.40625 0 Z M 1.40625 -6.203125 L 2.640625 -6.203125 L 2.640625 -4.71875 L 1.40625 -4.71875 Z M 1.40625 -6.203125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-15">
<path style="stroke:none;" d="M 1.484375 -1 L 3.421875 -1 L 3.421875 -7.671875 L 1.3125 -7.25 L 1.3125 -8.328125 L 3.40625 -8.75 L 4.59375 -8.75 L 4.59375 -1 L 6.53125 -1 L 6.53125 0 L 1.484375 0 Z M 1.484375 -1 "/>
</symbol>
<symbol overflow="visible" id="glyph0-16">
<path style="stroke:none;" d="M 3.8125 -7.96875 C 3.207031 -7.96875 2.75 -7.664062 2.4375 -7.0625 C 2.132812 -6.46875 1.984375 -5.566406 1.984375 -4.359375 C 1.984375 -3.160156 2.132812 -2.257812 2.4375 -1.65625 C 2.75 -1.0625 3.207031 -0.765625 3.8125 -0.765625 C 4.425781 -0.765625 4.882812 -1.0625 5.1875 -1.65625 C 5.5 -2.257812 5.65625 -3.160156 5.65625 -4.359375 C 5.65625 -5.566406 5.5 -6.46875 5.1875 -7.0625 C 4.882812 -7.664062 4.425781 -7.96875 3.8125 -7.96875 Z M 3.8125 -8.90625 C 4.789062 -8.90625 5.539062 -8.515625 6.0625 -7.734375 C 6.582031 -6.960938 6.84375 -5.835938 6.84375 -4.359375 C 6.84375 -2.890625 6.582031 -1.765625 6.0625 -0.984375 C 5.539062 -0.210938 4.789062 0.171875 3.8125 0.171875 C 2.832031 0.171875 2.082031 -0.210938 1.5625 -0.984375 C 1.050781 -1.765625 0.796875 -2.890625 0.796875 -4.359375 C 0.796875 -5.835938 1.050781 -6.960938 1.5625 -7.734375 C 2.082031 -8.515625 2.832031 -8.90625 3.8125 -8.90625 Z M 3.8125 -8.90625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-17">
<path style="stroke:none;" d="M 1.015625 -2.59375 L 1.015625 -6.5625 L 2.09375 -6.5625 L 2.09375 -2.625 C 2.09375 -2.007812 2.210938 -1.546875 2.453125 -1.234375 C 2.703125 -0.921875 3.066406 -0.765625 3.546875 -0.765625 C 4.128906 -0.765625 4.585938 -0.945312 4.921875 -1.3125 C 5.265625 -1.6875 5.4375 -2.195312 5.4375 -2.84375 L 5.4375 -6.5625 L 6.515625 -6.5625 L 6.515625 0 L 5.4375 0 L 5.4375 -1.015625 C 5.175781 -0.609375 4.875 -0.304688 4.53125 -0.109375 C 4.1875 0.078125 3.785156 0.171875 3.328125 0.171875 C 2.566406 0.171875 1.988281 -0.0625 1.59375 -0.53125 C 1.207031 -1 1.015625 -1.6875 1.015625 -2.59375 Z M 3.734375 -6.71875 Z M 3.734375 -6.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-18">
<path style="stroke:none;" d="M 3.859375 0.609375 C 3.554688 1.390625 3.257812 1.898438 2.96875 2.140625 C 2.675781 2.378906 2.289062 2.5 1.8125 2.5 L 0.953125 2.5 L 0.953125 1.59375 L 1.578125 1.59375 C 1.878906 1.59375 2.109375 1.519531 2.265625 1.375 C 2.429688 1.238281 2.613281 0.910156 2.8125 0.390625 L 3.015625 -0.109375 L 0.359375 -6.5625 L 1.5 -6.5625 L 3.546875 -1.4375 L 5.609375 -6.5625 L 6.75 -6.5625 Z M 3.859375 0.609375 "/>
</symbol>
<symbol overflow="visible" id="glyph0-19">
<path style="stroke:none;" d="M 1.265625 -5.453125 L 8.78125 -5.453125 L 8.78125 -4.46875 L 1.265625 -4.46875 Z M 1.265625 -3.0625 L 8.78125 -3.0625 L 8.78125 -2.0625 L 1.265625 -2.0625 Z M 1.265625 -3.0625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-20">
<path style="stroke:none;" d="M 2.296875 -1 L 6.4375 -1 L 6.4375 0 L 0.875 0 L 0.875 -1 C 1.320312 -1.457031 1.929688 -2.078125 2.703125 -2.859375 C 3.484375 -3.648438 3.972656 -4.160156 4.171875 -4.390625 C 4.554688 -4.804688 4.820312 -5.160156 4.96875 -5.453125 C 5.125 -5.753906 5.203125 -6.046875 5.203125 -6.328125 C 5.203125 -6.796875 5.035156 -7.175781 4.703125 -7.46875 C 4.378906 -7.757812 3.957031 -7.90625 3.4375 -7.90625 C 3.0625 -7.90625 2.664062 -7.84375 2.25 -7.71875 C 1.84375 -7.59375 1.40625 -7.394531 0.9375 -7.125 L 0.9375 -8.328125 C 1.414062 -8.515625 1.859375 -8.65625 2.265625 -8.75 C 2.679688 -8.851562 3.0625 -8.90625 3.40625 -8.90625 C 4.3125 -8.90625 5.035156 -8.675781 5.578125 -8.21875 C 6.117188 -7.769531 6.390625 -7.164062 6.390625 -6.40625 C 6.390625 -6.050781 6.320312 -5.710938 6.1875 -5.390625 C 6.050781 -5.066406 5.804688 -4.6875 5.453125 -4.25 C 5.347656 -4.132812 5.035156 -3.804688 4.515625 -3.265625 C 3.992188 -2.722656 3.253906 -1.96875 2.296875 -1 Z M 2.296875 -1 "/>
</symbol>
<symbol overflow="visible" id="glyph0-21">
<path style="stroke:none;" d="M 4.875 -4.71875 C 5.4375 -4.59375 5.875 -4.335938 6.1875 -3.953125 C 6.507812 -3.578125 6.671875 -3.109375 6.671875 -2.546875 C 6.671875 -1.679688 6.375 -1.007812 5.78125 -0.53125 C 5.1875 -0.0625 4.34375 0.171875 3.25 0.171875 C 2.882812 0.171875 2.503906 0.132812 2.109375 0.0625 C 1.722656 -0.0078125 1.328125 -0.117188 0.921875 -0.265625 L 0.921875 -1.40625 C 1.242188 -1.21875 1.597656 -1.070312 1.984375 -0.96875 C 2.378906 -0.875 2.789062 -0.828125 3.21875 -0.828125 C 3.957031 -0.828125 4.519531 -0.972656 4.90625 -1.265625 C 5.300781 -1.554688 5.5 -1.984375 5.5 -2.546875 C 5.5 -3.054688 5.316406 -3.457031 4.953125 -3.75 C 4.585938 -4.039062 4.085938 -4.1875 3.453125 -4.1875 L 2.421875 -4.1875 L 2.421875 -5.15625 L 3.5 -5.15625 C 4.070312 -5.15625 4.515625 -5.269531 4.828125 -5.5 C 5.140625 -5.738281 5.296875 -6.078125 5.296875 -6.515625 C 5.296875 -6.960938 5.132812 -7.304688 4.8125 -7.546875 C 4.5 -7.785156 4.046875 -7.90625 3.453125 -7.90625 C 3.117188 -7.90625 2.765625 -7.867188 2.390625 -7.796875 C 2.023438 -7.734375 1.617188 -7.628906 1.171875 -7.484375 L 1.171875 -8.53125 C 1.628906 -8.65625 2.050781 -8.75 2.4375 -8.8125 C 2.832031 -8.875 3.203125 -8.90625 3.546875 -8.90625 C 4.453125 -8.90625 5.164062 -8.703125 5.6875 -8.296875 C 6.207031 -7.890625 6.46875 -7.335938 6.46875 -6.640625 C 6.46875 -6.148438 6.328125 -5.738281 6.046875 -5.40625 C 5.773438 -5.070312 5.382812 -4.84375 4.875 -4.71875 Z M 4.875 -4.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-22">
<path style="stroke:none;" d="M 6.59375 -6.5625 L 4.21875 -3.375 L 6.703125 0 L 5.4375 0 L 3.53125 -2.578125 L 1.625 0 L 0.34375 0 L 2.890625 -3.4375 L 0.5625 -6.5625 L 1.828125 -6.5625 L 3.578125 -4.21875 L 5.3125 -6.5625 Z M 6.59375 -6.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-23">
<path style="stroke:none;" d="M 2.171875 -0.984375 L 2.171875 2.5 L 1.09375 2.5 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.5625 C 2.398438 -5.957031 2.6875 -6.25 3.03125 -6.4375 C 3.375 -6.625 3.785156 -6.71875 4.265625 -6.71875 C 5.066406 -6.71875 5.71875 -6.398438 6.21875 -5.765625 C 6.71875 -5.140625 6.96875 -4.3125 6.96875 -3.28125 C 6.96875 -2.25 6.71875 -1.414062 6.21875 -0.78125 C 5.71875 -0.144531 5.066406 0.171875 4.265625 0.171875 C 3.785156 0.171875 3.375 0.078125 3.03125 -0.109375 C 2.6875 -0.304688 2.398438 -0.597656 2.171875 -0.984375 Z M 5.84375 -3.28125 C 5.84375 -4.070312 5.675781 -4.691406 5.34375 -5.140625 C 5.019531 -5.585938 4.578125 -5.8125 4.015625 -5.8125 C 3.441406 -5.8125 2.988281 -5.585938 2.65625 -5.140625 C 2.332031 -4.691406 2.171875 -4.070312 2.171875 -3.28125 C 2.171875 -2.488281 2.332031 -1.863281 2.65625 -1.40625 C 2.988281 -0.957031 3.441406 -0.734375 4.015625 -0.734375 C 4.578125 -0.734375 5.019531 -0.957031 5.34375 -1.40625 C 5.675781 -1.863281 5.84375 -2.488281 5.84375 -3.28125 Z M 5.84375 -3.28125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-24">
<path style="stroke:none;" d="M 6.75 -3.546875 L 6.75 -3.03125 L 1.78125 -3.03125 C 1.832031 -2.28125 2.054688 -1.710938 2.453125 -1.328125 C 2.859375 -0.941406 3.414062 -0.75 4.125 -0.75 C 4.539062 -0.75 4.941406 -0.796875 5.328125 -0.890625 C 5.722656 -0.992188 6.113281 -1.148438 6.5 -1.359375 L 6.5 -0.328125 C 6.101562 -0.171875 5.703125 -0.0507812 5.296875 0.03125 C 4.890625 0.125 4.476562 0.171875 4.0625 0.171875 C 3.019531 0.171875 2.191406 -0.128906 1.578125 -0.734375 C 0.960938 -1.347656 0.65625 -2.175781 0.65625 -3.21875 C 0.65625 -4.289062 0.945312 -5.140625 1.53125 -5.765625 C 2.113281 -6.398438 2.894531 -6.71875 3.875 -6.71875 C 4.757812 -6.71875 5.457031 -6.429688 5.96875 -5.859375 C 6.488281 -5.296875 6.75 -4.523438 6.75 -3.546875 Z M 5.671875 -3.875 C 5.660156 -4.457031 5.492188 -4.925781 5.171875 -5.28125 C 4.847656 -5.632812 4.421875 -5.8125 3.890625 -5.8125 C 3.285156 -5.8125 2.800781 -5.640625 2.4375 -5.296875 C 2.082031 -4.953125 1.878906 -4.472656 1.828125 -3.859375 Z M 5.671875 -3.875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-25">
<path style="stroke:none;" d="M 2.203125 -8.421875 L 2.203125 -6.5625 L 4.421875 -6.5625 L 4.421875 -5.71875 L 2.203125 -5.71875 L 2.203125 -2.15625 C 2.203125 -1.625 2.273438 -1.28125 2.421875 -1.125 C 2.566406 -0.976562 2.863281 -0.90625 3.3125 -0.90625 L 4.421875 -0.90625 L 4.421875 0 L 3.3125 0 C 2.476562 0 1.898438 -0.15625 1.578125 -0.46875 C 1.265625 -0.78125 1.109375 -1.34375 1.109375 -2.15625 L 1.109375 -5.71875 L 0.328125 -5.71875 L 0.328125 -6.5625 L 1.109375 -6.5625 L 1.109375 -8.421875 Z M 2.203125 -8.421875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-26">
<path style="stroke:none;" d="M 1.09375 -9.125 L 2.171875 -9.125 L 2.171875 -3.734375 L 5.390625 -6.5625 L 6.765625 -6.5625 L 3.28125 -3.5 L 6.921875 0 L 5.515625 0 L 2.171875 -3.203125 L 2.171875 0 L 1.09375 0 Z M 1.09375 -9.125 "/>
</symbol>
</g>
<clipPath id="clip1">
<path d="M 1 0 L 579 0 L 579 132 L 1 132 Z M 1 0 "/>
</clipPath>
</defs>
<g id="surface845">
<g clip-path="url(#clip1)" clip-rule="nonzero">
<rect x="0" y="0" width="580" height="132" style="fill:rgb(90%,90%,90%);fill-opacity:1;stroke:none;"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 2.59375 0 L 2.59375 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 34.976562 0 L 34.976562 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 67.363281 0 L 67.363281 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 99.75 0 L 99.75 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 132.132812 0 L 132.132812 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 164.519531 0 L 164.519531 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 196.90625 0 L 196.90625 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 229.289062 0 L 229.289062 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 261.675781 0 L 261.675781 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 294.0625 0 L 294.0625 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 326.445312 0 L 326.445312 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 358.832031 0 L 358.832031 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 391.21875 0 L 391.21875 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 423.601562 0 L 423.601562 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 455.988281 0 L 455.988281 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 488.371094 0 L 488.371094 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 520.757812 0 L 520.757812 132 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 553.144531 0 L 553.144531 132 "/>
<g style="fill:rgb(40%,40%,40%);fill-opacity:1;">
<use xlink:href="#glyph0-1" x="4" y="127.503906"/>
<use xlink:href="#glyph0-2" x="11.582031" y="127.503906"/>
<use xlink:href="#glyph0-3" x="18.935547" y="127.503906"/>
<use xlink:href="#glyph0-4" x="25.533203" y="127.503906"/>
<use xlink:href="#glyph0-5" x="33.138672" y="127.503906"/>
<use xlink:href="#glyph0-6" x="36.953125" y="127.503906"/>
<use xlink:href="#glyph0-7" x="44.570312" y="127.503906"/>
<use xlink:href="#glyph0-8" x="49.503906" y="127.503906"/>
<use xlink:href="#glyph0-9" x="52.837891" y="127.503906"/>
<use xlink:href="#glyph0-5" x="60.455078" y="127.503906"/>
<use xlink:href="#glyph0-9" x="64.269531" y="127.503906"/>
<use xlink:href="#glyph0-8" x="71.886719" y="127.503906"/>
<use xlink:href="#glyph0-10" x="75.220703" y="127.503906"/>
<use xlink:href="#glyph0-8" x="82.322266" y="127.503906"/>
<use xlink:href="#glyph0-11" x="85.65625" y="127.503906"/>
<use xlink:href="#glyph0-8" x="91.908203" y="127.503906"/>
<use xlink:href="#glyph0-12" x="95.242188" y="127.503906"/>
<use xlink:href="#glyph0-13" x="102.583984" y="127.503906"/>
<use xlink:href="#glyph0-14" x="110.189453" y="127.503906"/>
<use xlink:href="#glyph0-5" x="114.232422" y="127.503906"/>
<use xlink:href="#glyph0-15" x="118.046875" y="127.503906"/>
<use xlink:href="#glyph0-16" x="125.681641" y="127.503906"/>
<use xlink:href="#glyph0-5" x="133.316406" y="127.503906"/>
<use xlink:href="#glyph0-17" x="137.130859" y="127.503906"/>
<use xlink:href="#glyph0-11" x="144.736328" y="127.503906"/>
</g>
<path style="fill:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(20%,20%,20%);stroke-opacity:1;stroke-miterlimit:10;" d="M 2.59375 31.183594 L 573.648438 31.183594 "/>
<path style="fill:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(20%,20%,20%);stroke-opacity:1;stroke-miterlimit:10;" d="M 38.8125 31.183594 L 38.8125 102 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(20%,20%,20%);fill-opacity:1;" d="M 573.648438 31.183594 L 567.648438 27.183594 L 567.648438 35.183594 "/>
<path style="fill:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(20%,20%,20%);stroke-opacity:1;stroke-miterlimit:10;" d="M 38.8125 102 L 557.929688 102 "/>
<path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" d="M 565.921875 31.183594 L 570.410156 31.183594 "/>
<path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" d="M 480.792969 31.183594 L 552.542969 31.183594 "/>
<path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" d="M 322.886719 31.183594 L 402.945312 31.183594 "/>
<path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" d="M 176.828125 31.183594 L 247.347656 31.183594 "/>
<path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" d="M 2.59375 31.183594 L 46.933594 31.183594 "/>
<path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" d="M 552.542969 102 L 557.929688 102 "/>
<path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" d="M 402.945312 102 L 480.792969 102 "/>
<path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" d="M 247.347656 102 L 322.886719 102 "/>
<path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" d="M 46.933594 102 L 176.828125 102 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(0%,0%,100%);stroke-opacity:0.99792;stroke-miterlimit:10;" d="M 557.929688 102 L 570.410156 41.183594 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,100%);fill-opacity:0.99792;" d="M 570.410156 31.183594 L 574.410156 41.183594 L 566.410156 41.183594 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="209.296875" y="28.183594"/>
<use xlink:href="#glyph0-5" x="216.398438" y="28.183594"/>
<use xlink:href="#glyph0-19" x="220.212891" y="28.183594"/>
<use xlink:href="#glyph0-5" x="230.267578" y="28.183594"/>
<use xlink:href="#glyph0-15" x="234.082031" y="28.183594"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 225.507812 28.183594 L 225.507812 34.183594 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="351.765625" y="28.183594"/>
<use xlink:href="#glyph0-5" x="358.867188" y="28.183594"/>
<use xlink:href="#glyph0-19" x="362.681641" y="28.183594"/>
<use xlink:href="#glyph0-5" x="372.736328" y="28.183594"/>
<use xlink:href="#glyph0-20" x="376.550781" y="28.183594"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 367.976562 28.183594 L 367.976562 34.183594 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="509.097656" y="28.183594"/>
<use xlink:href="#glyph0-5" x="516.199219" y="28.183594"/>
<use xlink:href="#glyph0-19" x="520.013672" y="28.183594"/>
<use xlink:href="#glyph0-5" x="530.068359" y="28.183594"/>
<use xlink:href="#glyph0-21" x="533.882812" y="28.183594"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 525.308594 28.183594 L 525.308594 34.183594 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-22" x="101.585938" y="99"/>
<use xlink:href="#glyph0-5" x="108.6875" y="99"/>
<use xlink:href="#glyph0-19" x="112.501953" y="99"/>
<use xlink:href="#glyph0-5" x="122.556641" y="99"/>
<use xlink:href="#glyph0-15" x="126.371094" y="99"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 117.792969 99 L 117.792969 105 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-22" x="274.625" y="99"/>
<use xlink:href="#glyph0-5" x="281.726562" y="99"/>
<use xlink:href="#glyph0-19" x="285.541016" y="99"/>
<use xlink:href="#glyph0-5" x="295.595703" y="99"/>
<use xlink:href="#glyph0-20" x="299.410156" y="99"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 290.835938 99 L 290.835938 105 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-22" x="433.574219" y="99"/>
<use xlink:href="#glyph0-5" x="440.675781" y="99"/>
<use xlink:href="#glyph0-19" x="444.490234" y="99"/>
<use xlink:href="#glyph0-5" x="454.544922" y="99"/>
<use xlink:href="#glyph0-21" x="458.359375" y="99"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 449.78125 99 L 449.78125 105 "/>
<g style="fill:rgb(50%,50%,50%);fill-opacity:1;">
<use xlink:href="#glyph0-23" x="4.59375" y="41.183594"/>
<use xlink:href="#glyph0-7" x="12.210938" y="41.183594"/>
<use xlink:href="#glyph0-24" x="17.144531" y="41.183594"/>
<use xlink:href="#glyph0-24" x="24.527344" y="41.183594"/>
<use xlink:href="#glyph0-22" x="31.910156" y="41.183594"/>
<use xlink:href="#glyph0-8" x="39.011719" y="41.183594"/>
<use xlink:href="#glyph0-11" x="42.345703" y="41.183594"/>
<use xlink:href="#glyph0-25" x="48.597656" y="41.183594"/>
<use xlink:href="#glyph0-8" x="53.302734" y="41.183594"/>
<use xlink:href="#glyph0-13" x="56.636719" y="41.183594"/>
<use xlink:href="#glyph0-6" x="64.242188" y="41.183594"/>
</g>
<g style="fill:rgb(50%,50%,50%);fill-opacity:1;">
<use xlink:href="#glyph0-25" x="40.8125" y="112"/>
<use xlink:href="#glyph0-2" x="45.517578" y="112"/>
<use xlink:href="#glyph0-11" x="52.871094" y="112"/>
<use xlink:href="#glyph0-26" x="59.123047" y="112"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 31 KiB

4
doc/traces/Makefile Normal file
View File

@ -0,0 +1,4 @@
all: both-posix.svg cancel-posix.svg switch-mock.svg net-posix.svg multicore-posix.svg
%.svg: %.fxt
eio-trace render "$<"

BIN
doc/traces/both-posix.fxt Normal file

Binary file not shown.

351
doc/traces/both-posix.svg Normal file
View File

@ -0,0 +1,351 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1280pt" height="120pt" viewBox="0 0 1280 120" version="1.1">
<defs>
<g>
<symbol overflow="visible" id="glyph0-0">
<path style="stroke:none;" d="M 0.59375 2.125 L 0.59375 -8.46875 L 6.59375 -8.46875 L 6.59375 2.125 Z M 1.265625 1.453125 L 5.9375 1.453125 L 5.9375 -7.78125 L 1.265625 -7.78125 Z M 1.265625 1.453125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-1">
<path style="stroke:none;" d="M 1.171875 -8.75 L 6.703125 -8.75 L 6.703125 -7.75 L 2.359375 -7.75 L 2.359375 -5.15625 L 6.53125 -5.15625 L 6.53125 -4.171875 L 2.359375 -4.171875 L 2.359375 -1 L 6.8125 -1 L 6.8125 0 L 1.171875 0 Z M 1.171875 -8.75 "/>
</symbol>
<symbol overflow="visible" id="glyph0-2">
<path style="stroke:none;" d="M 4.109375 -3.296875 C 3.242188 -3.296875 2.640625 -3.195312 2.296875 -3 C 1.960938 -2.800781 1.796875 -2.460938 1.796875 -1.984375 C 1.796875 -1.597656 1.921875 -1.289062 2.171875 -1.0625 C 2.429688 -0.84375 2.773438 -0.734375 3.203125 -0.734375 C 3.804688 -0.734375 4.285156 -0.941406 4.640625 -1.359375 C 5.003906 -1.785156 5.1875 -2.351562 5.1875 -3.0625 L 5.1875 -3.296875 Z M 6.265625 -3.75 L 6.265625 0 L 5.1875 0 L 5.1875 -1 C 4.9375 -0.601562 4.628906 -0.304688 4.265625 -0.109375 C 3.898438 0.078125 3.453125 0.171875 2.921875 0.171875 C 2.242188 0.171875 1.707031 -0.015625 1.3125 -0.390625 C 0.914062 -0.773438 0.71875 -1.28125 0.71875 -1.90625 C 0.71875 -2.644531 0.960938 -3.203125 1.453125 -3.578125 C 1.953125 -3.953125 2.691406 -4.140625 3.671875 -4.140625 L 5.1875 -4.140625 L 5.1875 -4.25 C 5.1875 -4.75 5.019531 -5.132812 4.6875 -5.40625 C 4.363281 -5.675781 3.910156 -5.8125 3.328125 -5.8125 C 2.953125 -5.8125 2.582031 -5.765625 2.21875 -5.671875 C 1.863281 -5.578125 1.523438 -5.441406 1.203125 -5.265625 L 1.203125 -6.265625 C 1.597656 -6.421875 1.976562 -6.535156 2.34375 -6.609375 C 2.71875 -6.679688 3.082031 -6.71875 3.4375 -6.71875 C 4.382812 -6.71875 5.09375 -6.472656 5.5625 -5.984375 C 6.03125 -5.492188 6.265625 -4.75 6.265625 -3.75 Z M 6.265625 -3.75 "/>
</symbol>
<symbol overflow="visible" id="glyph0-3">
<path style="stroke:none;" d="M 5.859375 -6.3125 L 5.859375 -5.296875 C 5.546875 -5.472656 5.238281 -5.601562 4.9375 -5.6875 C 4.632812 -5.769531 4.328125 -5.8125 4.015625 -5.8125 C 3.304688 -5.8125 2.757812 -5.585938 2.375 -5.140625 C 1.988281 -4.703125 1.796875 -4.082031 1.796875 -3.28125 C 1.796875 -2.476562 1.988281 -1.851562 2.375 -1.40625 C 2.757812 -0.96875 3.304688 -0.75 4.015625 -0.75 C 4.328125 -0.75 4.632812 -0.789062 4.9375 -0.875 C 5.238281 -0.957031 5.546875 -1.082031 5.859375 -1.25 L 5.859375 -0.25 C 5.554688 -0.113281 5.242188 -0.0078125 4.921875 0.0625 C 4.597656 0.132812 4.253906 0.171875 3.890625 0.171875 C 2.898438 0.171875 2.113281 -0.132812 1.53125 -0.75 C 0.945312 -1.375 0.65625 -2.21875 0.65625 -3.28125 C 0.65625 -4.34375 0.945312 -5.179688 1.53125 -5.796875 C 2.125 -6.410156 2.9375 -6.71875 3.96875 -6.71875 C 4.289062 -6.71875 4.609375 -6.679688 4.921875 -6.609375 C 5.242188 -6.546875 5.554688 -6.445312 5.859375 -6.3125 Z M 5.859375 -6.3125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-4">
<path style="stroke:none;" d="M 6.59375 -3.96875 L 6.59375 0 L 5.515625 0 L 5.515625 -3.921875 C 5.515625 -4.546875 5.390625 -5.007812 5.140625 -5.3125 C 4.898438 -5.625 4.539062 -5.78125 4.0625 -5.78125 C 3.476562 -5.78125 3.015625 -5.59375 2.671875 -5.21875 C 2.335938 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -9.125 L 2.171875 -9.125 L 2.171875 -5.546875 C 2.429688 -5.941406 2.734375 -6.234375 3.078125 -6.421875 C 3.429688 -6.617188 3.835938 -6.71875 4.296875 -6.71875 C 5.046875 -6.71875 5.613281 -6.484375 6 -6.015625 C 6.394531 -5.554688 6.59375 -4.875 6.59375 -3.96875 Z M 6.59375 -3.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-5">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph0-6">
<path style="stroke:none;" d="M 5.453125 -3.359375 C 5.453125 -4.140625 5.289062 -4.742188 4.96875 -5.171875 C 4.644531 -5.597656 4.191406 -5.8125 3.609375 -5.8125 C 3.035156 -5.8125 2.585938 -5.597656 2.265625 -5.171875 C 1.941406 -4.742188 1.78125 -4.140625 1.78125 -3.359375 C 1.78125 -2.578125 1.941406 -1.972656 2.265625 -1.546875 C 2.585938 -1.117188 3.035156 -0.90625 3.609375 -0.90625 C 4.191406 -0.90625 4.644531 -1.117188 4.96875 -1.546875 C 5.289062 -1.972656 5.453125 -2.578125 5.453125 -3.359375 Z M 6.53125 -0.8125 C 6.53125 0.300781 6.28125 1.128906 5.78125 1.671875 C 5.289062 2.222656 4.53125 2.5 3.5 2.5 C 3.125 2.5 2.765625 2.46875 2.421875 2.40625 C 2.085938 2.351562 1.765625 2.269531 1.453125 2.15625 L 1.453125 1.109375 C 1.765625 1.273438 2.078125 1.398438 2.390625 1.484375 C 2.703125 1.566406 3.015625 1.609375 3.328125 1.609375 C 4.035156 1.609375 4.566406 1.421875 4.921875 1.046875 C 5.273438 0.679688 5.453125 0.125 5.453125 -0.625 L 5.453125 -1.15625 C 5.222656 -0.769531 4.9375 -0.476562 4.59375 -0.28125 C 4.25 -0.09375 3.832031 0 3.34375 0 C 2.539062 0 1.890625 -0.304688 1.390625 -0.921875 C 0.898438 -1.535156 0.65625 -2.347656 0.65625 -3.359375 C 0.65625 -4.367188 0.898438 -5.179688 1.390625 -5.796875 C 1.890625 -6.410156 2.539062 -6.71875 3.34375 -6.71875 C 3.832031 -6.71875 4.25 -6.617188 4.59375 -6.421875 C 4.9375 -6.234375 5.222656 -5.945312 5.453125 -5.5625 L 5.453125 -6.5625 L 6.53125 -6.5625 Z M 6.53125 -0.8125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-7">
<path style="stroke:none;" d="M 4.9375 -5.5625 C 4.8125 -5.625 4.675781 -5.671875 4.53125 -5.703125 C 4.394531 -5.742188 4.238281 -5.765625 4.0625 -5.765625 C 3.457031 -5.765625 2.988281 -5.566406 2.65625 -5.171875 C 2.332031 -4.773438 2.171875 -4.203125 2.171875 -3.453125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.398438 -5.941406 2.695312 -6.234375 3.0625 -6.421875 C 3.425781 -6.617188 3.867188 -6.71875 4.390625 -6.71875 C 4.460938 -6.71875 4.539062 -6.710938 4.625 -6.703125 C 4.71875 -6.691406 4.816406 -6.675781 4.921875 -6.65625 Z M 4.9375 -5.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-8">
<path style="stroke:none;" d="M 1.125 -6.5625 L 2.203125 -6.5625 L 2.203125 0 L 1.125 0 Z M 1.125 -9.125 L 2.203125 -9.125 L 2.203125 -7.75 L 1.125 -7.75 Z M 1.125 -9.125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-9">
<path style="stroke:none;" d="M 5.453125 -5.5625 L 5.453125 -9.125 L 6.53125 -9.125 L 6.53125 0 L 5.453125 0 L 5.453125 -0.984375 C 5.222656 -0.597656 4.9375 -0.304688 4.59375 -0.109375 C 4.25 0.078125 3.832031 0.171875 3.34375 0.171875 C 2.550781 0.171875 1.90625 -0.144531 1.40625 -0.78125 C 0.90625 -1.414062 0.65625 -2.25 0.65625 -3.28125 C 0.65625 -4.3125 0.90625 -5.140625 1.40625 -5.765625 C 1.90625 -6.398438 2.550781 -6.71875 3.34375 -6.71875 C 3.832031 -6.71875 4.25 -6.625 4.59375 -6.4375 C 4.9375 -6.25 5.222656 -5.957031 5.453125 -5.5625 Z M 1.78125 -3.28125 C 1.78125 -2.488281 1.941406 -1.863281 2.265625 -1.40625 C 2.585938 -0.957031 3.035156 -0.734375 3.609375 -0.734375 C 4.179688 -0.734375 4.628906 -0.957031 4.953125 -1.40625 C 5.285156 -1.863281 5.453125 -2.488281 5.453125 -3.28125 C 5.453125 -4.070312 5.285156 -4.691406 4.953125 -5.140625 C 4.628906 -5.585938 4.179688 -5.8125 3.609375 -5.8125 C 3.035156 -5.8125 2.585938 -5.585938 2.265625 -5.140625 C 1.941406 -4.691406 1.78125 -4.070312 1.78125 -3.28125 Z M 1.78125 -3.28125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-10">
<path style="stroke:none;" d="M 0.359375 -6.5625 L 1.5 -6.5625 L 3.546875 -1.0625 L 5.609375 -6.5625 L 6.75 -6.5625 L 4.28125 0 L 2.8125 0 Z M 0.359375 -6.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-11">
<path style="stroke:none;" d="M 5.3125 -6.375 L 5.3125 -5.34375 C 5.007812 -5.5 4.691406 -5.613281 4.359375 -5.6875 C 4.035156 -5.769531 3.695312 -5.8125 3.34375 -5.8125 C 2.8125 -5.8125 2.410156 -5.726562 2.140625 -5.5625 C 1.867188 -5.40625 1.734375 -5.160156 1.734375 -4.828125 C 1.734375 -4.578125 1.828125 -4.378906 2.015625 -4.234375 C 2.210938 -4.097656 2.601562 -3.96875 3.1875 -3.84375 L 3.546875 -3.75 C 4.316406 -3.59375 4.863281 -3.363281 5.1875 -3.0625 C 5.507812 -2.757812 5.671875 -2.34375 5.671875 -1.8125 C 5.671875 -1.195312 5.425781 -0.710938 4.9375 -0.359375 C 4.457031 -0.00390625 3.796875 0.171875 2.953125 0.171875 C 2.597656 0.171875 2.226562 0.132812 1.84375 0.0625 C 1.46875 0 1.070312 -0.0976562 0.65625 -0.234375 L 0.65625 -1.359375 C 1.050781 -1.148438 1.441406 -0.992188 1.828125 -0.890625 C 2.210938 -0.785156 2.597656 -0.734375 2.984375 -0.734375 C 3.484375 -0.734375 3.867188 -0.816406 4.140625 -0.984375 C 4.421875 -1.160156 4.5625 -1.410156 4.5625 -1.734375 C 4.5625 -2.023438 4.460938 -2.25 4.265625 -2.40625 C 4.066406 -2.5625 3.632812 -2.710938 2.96875 -2.859375 L 2.59375 -2.9375 C 1.925781 -3.082031 1.441406 -3.300781 1.140625 -3.59375 C 0.847656 -3.882812 0.703125 -4.28125 0.703125 -4.78125 C 0.703125 -5.40625 0.921875 -5.882812 1.359375 -6.21875 C 1.796875 -6.550781 2.414062 -6.71875 3.21875 -6.71875 C 3.613281 -6.71875 3.988281 -6.6875 4.34375 -6.625 C 4.695312 -6.570312 5.019531 -6.488281 5.3125 -6.375 Z M 5.3125 -6.375 "/>
</symbol>
<symbol overflow="visible" id="glyph0-12">
<path style="stroke:none;" d="M 3.671875 -5.8125 C 3.097656 -5.8125 2.640625 -5.582031 2.296875 -5.125 C 1.960938 -4.675781 1.796875 -4.0625 1.796875 -3.28125 C 1.796875 -2.488281 1.960938 -1.867188 2.296875 -1.421875 C 2.628906 -0.972656 3.085938 -0.75 3.671875 -0.75 C 4.242188 -0.75 4.695312 -0.972656 5.03125 -1.421875 C 5.375 -1.878906 5.546875 -2.5 5.546875 -3.28125 C 5.546875 -4.050781 5.375 -4.664062 5.03125 -5.125 C 4.695312 -5.582031 4.242188 -5.8125 3.671875 -5.8125 Z M 3.671875 -6.71875 C 4.609375 -6.71875 5.34375 -6.410156 5.875 -5.796875 C 6.414062 -5.191406 6.6875 -4.351562 6.6875 -3.28125 C 6.6875 -2.207031 6.414062 -1.363281 5.875 -0.75 C 5.34375 -0.132812 4.609375 0.171875 3.671875 0.171875 C 2.734375 0.171875 1.992188 -0.132812 1.453125 -0.75 C 0.921875 -1.363281 0.65625 -2.207031 0.65625 -3.28125 C 0.65625 -4.351562 0.921875 -5.191406 1.453125 -5.796875 C 1.992188 -6.410156 2.734375 -6.71875 3.671875 -6.71875 Z M 3.671875 -6.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-13">
<path style="stroke:none;" d="M 6.59375 -3.96875 L 6.59375 0 L 5.515625 0 L 5.515625 -3.921875 C 5.515625 -4.546875 5.390625 -5.007812 5.140625 -5.3125 C 4.898438 -5.625 4.539062 -5.78125 4.0625 -5.78125 C 3.476562 -5.78125 3.015625 -5.59375 2.671875 -5.21875 C 2.335938 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.429688 -5.941406 2.734375 -6.234375 3.078125 -6.421875 C 3.429688 -6.617188 3.835938 -6.71875 4.296875 -6.71875 C 5.046875 -6.71875 5.613281 -6.484375 6 -6.015625 C 6.394531 -5.554688 6.59375 -4.875 6.59375 -3.96875 Z M 6.59375 -3.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-14">
<path style="stroke:none;" d="M 1.40625 -1.484375 L 2.640625 -1.484375 L 2.640625 0 L 1.40625 0 Z M 1.40625 -6.203125 L 2.640625 -6.203125 L 2.640625 -4.71875 L 1.40625 -4.71875 Z M 1.40625 -6.203125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-15">
<path style="stroke:none;" d="M 1.484375 -1 L 3.421875 -1 L 3.421875 -7.671875 L 1.3125 -7.25 L 1.3125 -8.328125 L 3.40625 -8.75 L 4.59375 -8.75 L 4.59375 -1 L 6.53125 -1 L 6.53125 0 L 1.484375 0 Z M 1.484375 -1 "/>
</symbol>
<symbol overflow="visible" id="glyph0-16">
<path style="stroke:none;" d="M 3.8125 -7.96875 C 3.207031 -7.96875 2.75 -7.664062 2.4375 -7.0625 C 2.132812 -6.46875 1.984375 -5.566406 1.984375 -4.359375 C 1.984375 -3.160156 2.132812 -2.257812 2.4375 -1.65625 C 2.75 -1.0625 3.207031 -0.765625 3.8125 -0.765625 C 4.425781 -0.765625 4.882812 -1.0625 5.1875 -1.65625 C 5.5 -2.257812 5.65625 -3.160156 5.65625 -4.359375 C 5.65625 -5.566406 5.5 -6.46875 5.1875 -7.0625 C 4.882812 -7.664062 4.425781 -7.96875 3.8125 -7.96875 Z M 3.8125 -8.90625 C 4.789062 -8.90625 5.539062 -8.515625 6.0625 -7.734375 C 6.582031 -6.960938 6.84375 -5.835938 6.84375 -4.359375 C 6.84375 -2.890625 6.582031 -1.765625 6.0625 -0.984375 C 5.539062 -0.210938 4.789062 0.171875 3.8125 0.171875 C 2.832031 0.171875 2.082031 -0.210938 1.5625 -0.984375 C 1.050781 -1.765625 0.796875 -2.890625 0.796875 -4.359375 C 0.796875 -5.835938 1.050781 -6.960938 1.5625 -7.734375 C 2.082031 -8.515625 2.832031 -8.90625 3.8125 -8.90625 Z M 3.8125 -8.90625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-17">
<path style="stroke:none;" d="M 1.015625 -2.59375 L 1.015625 -6.5625 L 2.09375 -6.5625 L 2.09375 -2.625 C 2.09375 -2.007812 2.210938 -1.546875 2.453125 -1.234375 C 2.703125 -0.921875 3.066406 -0.765625 3.546875 -0.765625 C 4.128906 -0.765625 4.585938 -0.945312 4.921875 -1.3125 C 5.265625 -1.6875 5.4375 -2.195312 5.4375 -2.84375 L 5.4375 -6.5625 L 6.515625 -6.5625 L 6.515625 0 L 5.4375 0 L 5.4375 -1.015625 C 5.175781 -0.609375 4.875 -0.304688 4.53125 -0.109375 C 4.1875 0.078125 3.785156 0.171875 3.328125 0.171875 C 2.566406 0.171875 1.988281 -0.0625 1.59375 -0.53125 C 1.207031 -1 1.015625 -1.6875 1.015625 -2.59375 Z M 3.734375 -6.71875 Z M 3.734375 -6.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-18">
<path style="stroke:none;" d="M 2.203125 -8.421875 L 2.203125 -6.5625 L 4.421875 -6.5625 L 4.421875 -5.71875 L 2.203125 -5.71875 L 2.203125 -2.15625 C 2.203125 -1.625 2.273438 -1.28125 2.421875 -1.125 C 2.566406 -0.976562 2.863281 -0.90625 3.3125 -0.90625 L 4.421875 -0.90625 L 4.421875 0 L 3.3125 0 C 2.476562 0 1.898438 -0.15625 1.578125 -0.46875 C 1.265625 -0.78125 1.109375 -1.34375 1.109375 -2.15625 L 1.109375 -5.71875 L 0.328125 -5.71875 L 0.328125 -6.5625 L 1.109375 -6.5625 L 1.109375 -8.421875 Z M 2.203125 -8.421875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-19">
<path style="stroke:none;" d="M 6.75 -3.546875 L 6.75 -3.03125 L 1.78125 -3.03125 C 1.832031 -2.28125 2.054688 -1.710938 2.453125 -1.328125 C 2.859375 -0.941406 3.414062 -0.75 4.125 -0.75 C 4.539062 -0.75 4.941406 -0.796875 5.328125 -0.890625 C 5.722656 -0.992188 6.113281 -1.148438 6.5 -1.359375 L 6.5 -0.328125 C 6.101562 -0.171875 5.703125 -0.0507812 5.296875 0.03125 C 4.890625 0.125 4.476562 0.171875 4.0625 0.171875 C 3.019531 0.171875 2.191406 -0.128906 1.578125 -0.734375 C 0.960938 -1.347656 0.65625 -2.175781 0.65625 -3.21875 C 0.65625 -4.289062 0.945312 -5.140625 1.53125 -5.765625 C 2.113281 -6.398438 2.894531 -6.71875 3.875 -6.71875 C 4.757812 -6.71875 5.457031 -6.429688 5.96875 -5.859375 C 6.488281 -5.296875 6.75 -4.523438 6.75 -3.546875 Z M 5.671875 -3.875 C 5.660156 -4.457031 5.492188 -4.925781 5.171875 -5.28125 C 4.847656 -5.632812 4.421875 -5.8125 3.890625 -5.8125 C 3.285156 -5.8125 2.800781 -5.640625 2.4375 -5.296875 C 2.082031 -4.953125 1.878906 -4.472656 1.828125 -3.859375 Z M 5.671875 -3.875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-20">
<path style="stroke:none;" d="M 1.125 -9.125 L 2.203125 -9.125 L 2.203125 0 L 1.125 0 Z M 1.125 -9.125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-0">
<path style="stroke:none;" d="M 0.40625 1.421875 L 0.40625 -5.640625 L 4.40625 -5.640625 L 4.40625 1.421875 Z M 0.84375 0.96875 L 3.953125 0.96875 L 3.953125 -5.1875 L 0.84375 -5.1875 Z M 0.84375 0.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-1">
<path style="stroke:none;" d="M 4.390625 -4.375 L 2.8125 -2.25 L 4.46875 0 L 3.625 0 L 2.359375 -1.71875 L 1.078125 0 L 0.234375 0 L 1.9375 -2.296875 L 0.375 -4.375 L 1.21875 -4.375 L 2.390625 -2.8125 L 3.546875 -4.375 Z M 4.390625 -4.375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-2">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph1-3">
<path style="stroke:none;" d="M 0.84375 -3.640625 L 5.859375 -3.640625 L 5.859375 -2.984375 L 0.84375 -2.984375 Z M 0.84375 -2.046875 L 5.859375 -2.046875 L 5.859375 -1.375 L 0.84375 -1.375 Z M 0.84375 -2.046875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-4">
<path style="stroke:none;" d="M 1 -0.671875 L 2.28125 -0.671875 L 2.28125 -5.109375 L 0.875 -4.828125 L 0.875 -5.546875 L 2.28125 -5.828125 L 3.0625 -5.828125 L 3.0625 -0.671875 L 4.359375 -0.671875 L 4.359375 0 L 1 0 Z M 1 -0.671875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-5">
<path style="stroke:none;" d="M 1.53125 -0.671875 L 4.296875 -0.671875 L 4.296875 0 L 0.59375 0 L 0.59375 -0.671875 C 0.882812 -0.972656 1.289062 -1.382812 1.8125 -1.90625 C 2.332031 -2.4375 2.65625 -2.773438 2.78125 -2.921875 C 3.039062 -3.203125 3.21875 -3.441406 3.3125 -3.640625 C 3.414062 -3.835938 3.46875 -4.03125 3.46875 -4.21875 C 3.46875 -4.53125 3.359375 -4.785156 3.140625 -4.984375 C 2.921875 -5.179688 2.640625 -5.28125 2.296875 -5.28125 C 2.046875 -5.28125 1.78125 -5.234375 1.5 -5.140625 C 1.226562 -5.054688 0.9375 -4.925781 0.625 -4.75 L 0.625 -5.546875 C 0.945312 -5.679688 1.242188 -5.78125 1.515625 -5.84375 C 1.796875 -5.90625 2.050781 -5.9375 2.28125 -5.9375 C 2.882812 -5.9375 3.363281 -5.785156 3.71875 -5.484375 C 4.082031 -5.179688 4.265625 -4.78125 4.265625 -4.28125 C 4.265625 -4.039062 4.21875 -3.8125 4.125 -3.59375 C 4.03125 -3.375 3.867188 -3.117188 3.640625 -2.828125 C 3.566406 -2.753906 3.351562 -2.535156 3 -2.171875 C 2.65625 -1.816406 2.164062 -1.316406 1.53125 -0.671875 Z M 1.53125 -0.671875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-6">
<path style="stroke:none;" d="M 3.25 -3.140625 C 3.625 -3.066406 3.914062 -2.898438 4.125 -2.640625 C 4.34375 -2.390625 4.453125 -2.078125 4.453125 -1.703125 C 4.453125 -1.117188 4.253906 -0.671875 3.859375 -0.359375 C 3.460938 -0.046875 2.898438 0.109375 2.171875 0.109375 C 1.921875 0.109375 1.664062 0.0820312 1.40625 0.03125 C 1.15625 -0.0078125 0.890625 -0.078125 0.609375 -0.171875 L 0.609375 -0.9375 C 0.828125 -0.8125 1.066406 -0.710938 1.328125 -0.640625 C 1.585938 -0.578125 1.859375 -0.546875 2.140625 -0.546875 C 2.640625 -0.546875 3.019531 -0.644531 3.28125 -0.84375 C 3.539062 -1.039062 3.671875 -1.328125 3.671875 -1.703125 C 3.671875 -2.046875 3.546875 -2.3125 3.296875 -2.5 C 3.054688 -2.695312 2.722656 -2.796875 2.296875 -2.796875 L 1.625 -2.796875 L 1.625 -3.4375 L 2.328125 -3.4375 C 2.710938 -3.4375 3.007812 -3.515625 3.21875 -3.671875 C 3.425781 -3.828125 3.53125 -4.050781 3.53125 -4.34375 C 3.53125 -4.644531 3.421875 -4.875 3.203125 -5.03125 C 2.992188 -5.195312 2.691406 -5.28125 2.296875 -5.28125 C 2.078125 -5.28125 1.84375 -5.253906 1.59375 -5.203125 C 1.351562 -5.160156 1.082031 -5.085938 0.78125 -4.984375 L 0.78125 -5.6875 C 1.082031 -5.769531 1.363281 -5.832031 1.625 -5.875 C 1.882812 -5.914062 2.132812 -5.9375 2.375 -5.9375 C 2.96875 -5.9375 3.4375 -5.800781 3.78125 -5.53125 C 4.132812 -5.257812 4.3125 -4.890625 4.3125 -4.421875 C 4.3125 -4.097656 4.21875 -3.828125 4.03125 -3.609375 C 3.851562 -3.390625 3.59375 -3.234375 3.25 -3.140625 Z M 3.25 -3.140625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-7">
<path style="stroke:none;" d="M 2.578125 0.40625 C 2.367188 0.925781 2.164062 1.265625 1.96875 1.421875 C 1.78125 1.585938 1.523438 1.671875 1.203125 1.671875 L 0.640625 1.671875 L 0.640625 1.0625 L 1.0625 1.0625 C 1.257812 1.0625 1.410156 1.015625 1.515625 0.921875 C 1.628906 0.828125 1.75 0.609375 1.875 0.265625 L 2.015625 -0.078125 L 0.234375 -4.375 L 1 -4.375 L 2.375 -0.953125 L 3.734375 -4.375 L 4.5 -4.375 Z M 2.578125 0.40625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-8">
<path style="stroke:none;" d="M 3.890625 -2.1875 C 3.890625 -2.71875 3.78125 -3.128906 3.5625 -3.421875 C 3.351562 -3.722656 3.054688 -3.875 2.671875 -3.875 C 2.296875 -3.875 2 -3.722656 1.78125 -3.421875 C 1.5625 -3.128906 1.453125 -2.71875 1.453125 -2.1875 C 1.453125 -1.65625 1.5625 -1.238281 1.78125 -0.9375 C 2 -0.632812 2.296875 -0.484375 2.671875 -0.484375 C 3.054688 -0.484375 3.351562 -0.632812 3.5625 -0.9375 C 3.78125 -1.238281 3.890625 -1.65625 3.890625 -2.1875 Z M 1.453125 -3.71875 C 1.597656 -3.976562 1.785156 -4.171875 2.015625 -4.296875 C 2.253906 -4.421875 2.53125 -4.484375 2.84375 -4.484375 C 3.375 -4.484375 3.804688 -4.269531 4.140625 -3.84375 C 4.472656 -3.425781 4.640625 -2.875 4.640625 -2.1875 C 4.640625 -1.5 4.472656 -0.941406 4.140625 -0.515625 C 3.804688 -0.0976562 3.375 0.109375 2.84375 0.109375 C 2.53125 0.109375 2.253906 0.046875 2.015625 -0.078125 C 1.785156 -0.203125 1.597656 -0.394531 1.453125 -0.65625 L 1.453125 0 L 0.734375 0 L 0.734375 -6.078125 L 1.453125 -6.078125 Z M 1.453125 -3.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-9">
<path style="stroke:none;" d="M 2.453125 -3.875 C 2.066406 -3.875 1.757812 -3.722656 1.53125 -3.421875 C 1.3125 -3.117188 1.203125 -2.707031 1.203125 -2.1875 C 1.203125 -1.664062 1.3125 -1.253906 1.53125 -0.953125 C 1.757812 -0.648438 2.066406 -0.5 2.453125 -0.5 C 2.835938 -0.5 3.140625 -0.648438 3.359375 -0.953125 C 3.585938 -1.253906 3.703125 -1.664062 3.703125 -2.1875 C 3.703125 -2.707031 3.585938 -3.117188 3.359375 -3.421875 C 3.140625 -3.722656 2.835938 -3.875 2.453125 -3.875 Z M 2.453125 -4.484375 C 3.078125 -4.484375 3.566406 -4.28125 3.921875 -3.875 C 4.273438 -3.46875 4.453125 -2.90625 4.453125 -2.1875 C 4.453125 -1.46875 4.273438 -0.90625 3.921875 -0.5 C 3.566406 -0.09375 3.078125 0.109375 2.453125 0.109375 C 1.828125 0.109375 1.332031 -0.09375 0.96875 -0.5 C 0.613281 -0.90625 0.4375 -1.46875 0.4375 -2.1875 C 0.4375 -2.90625 0.613281 -3.46875 0.96875 -3.875 C 1.332031 -4.28125 1.828125 -4.484375 2.453125 -4.484375 Z M 2.453125 -4.484375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-10">
<path style="stroke:none;" d="M 1.46875 -5.625 L 1.46875 -4.375 L 2.953125 -4.375 L 2.953125 -3.8125 L 1.46875 -3.8125 L 1.46875 -1.4375 C 1.46875 -1.082031 1.515625 -0.851562 1.609375 -0.75 C 1.710938 -0.65625 1.910156 -0.609375 2.203125 -0.609375 L 2.953125 -0.609375 L 2.953125 0 L 2.203125 0 C 1.648438 0 1.269531 -0.101562 1.0625 -0.3125 C 0.851562 -0.519531 0.75 -0.894531 0.75 -1.4375 L 0.75 -3.8125 L 0.21875 -3.8125 L 0.21875 -4.375 L 0.75 -4.375 L 0.75 -5.625 Z M 1.46875 -5.625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-11">
<path style="stroke:none;" d="M 4.390625 -2.640625 L 4.390625 0 L 3.671875 0 L 3.671875 -2.625 C 3.671875 -3.03125 3.585938 -3.335938 3.421875 -3.546875 C 3.265625 -3.753906 3.023438 -3.859375 2.703125 -3.859375 C 2.316406 -3.859375 2.007812 -3.734375 1.78125 -3.484375 C 1.5625 -3.234375 1.453125 -2.894531 1.453125 -2.46875 L 1.453125 0 L 0.734375 0 L 0.734375 -6.078125 L 1.453125 -6.078125 L 1.453125 -3.703125 C 1.617188 -3.960938 1.816406 -4.15625 2.046875 -4.28125 C 2.285156 -4.414062 2.554688 -4.484375 2.859375 -4.484375 C 3.367188 -4.484375 3.75 -4.328125 4 -4.015625 C 4.257812 -3.703125 4.390625 -3.242188 4.390625 -2.640625 Z M 4.390625 -2.640625 "/>
</symbol>
</g>
<clipPath id="clip1">
<path d="M 692.644531 48 L 734 48 L 734 59 L 692.644531 59 Z M 692.644531 48 "/>
</clipPath>
<clipPath id="clip2">
<path d="M 885.316406 48 L 927 48 L 927 59 L 885.316406 59 Z M 885.316406 48 "/>
</clipPath>
<clipPath id="clip3">
<path d="M 1038.214844 48 L 1060.753906 48 L 1060.753906 59 L 1038.214844 59 Z M 1038.214844 48 "/>
</clipPath>
<clipPath id="clip4">
<path d="M 819.328125 80 L 861 80 L 861 91 L 819.328125 91 Z M 819.328125 80 "/>
</clipPath>
<clipPath id="clip5">
<path d="M 967 80 L 1006.179688 80 L 1006.179688 91 L 967 91 Z M 967 80 "/>
</clipPath>
<clipPath id="clip6">
<path d="M 433.214844 70 L 455 70 L 455 76 L 433.214844 76 Z M 433.214844 70 "/>
</clipPath>
<clipPath id="clip7">
<path d="M 854.011719 70 L 875 70 L 875 76 L 854.011719 76 Z M 854.011719 70 "/>
</clipPath>
<clipPath id="clip8">
<path d="M 993.652344 70 L 1015 70 L 1015 77 L 993.652344 77 Z M 993.652344 70 "/>
</clipPath>
<clipPath id="clip9">
<path d="M 738.246094 38 L 760 38 L 760 46 L 738.246094 46 Z M 738.246094 38 "/>
</clipPath>
<clipPath id="clip10">
<path d="M 937.078125 38 L 958 38 L 958 46 L 937.078125 46 Z M 937.078125 38 "/>
</clipPath>
<clipPath id="clip11">
<path d="M 1047.515625 38 L 1069 38 L 1069 46 L 1047.515625 46 Z M 1047.515625 38 "/>
</clipPath>
</defs>
<g id="surface2">
<rect x="0" y="0" width="1280" height="120" style="fill:rgb(90%,90%,90%);fill-opacity:1;stroke:none;"/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 4 0 L 4 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 169.472656 0 L 169.472656 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 334.945312 0 L 334.945312 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 500.417969 0 L 500.417969 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 665.886719 0 L 665.886719 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 831.359375 0 L 831.359375 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 996.832031 0 L 996.832031 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1162.304688 0 L 1162.304688 120 "/>
<g style="fill:rgb(40%,40%,40%);fill-opacity:1;">
<use xlink:href="#glyph0-1" x="4" y="115.503906"/>
<use xlink:href="#glyph0-2" x="11.582031" y="115.503906"/>
<use xlink:href="#glyph0-3" x="18.935547" y="115.503906"/>
<use xlink:href="#glyph0-4" x="25.533203" y="115.503906"/>
<use xlink:href="#glyph0-5" x="33.138672" y="115.503906"/>
<use xlink:href="#glyph0-6" x="36.953125" y="115.503906"/>
<use xlink:href="#glyph0-7" x="44.570312" y="115.503906"/>
<use xlink:href="#glyph0-8" x="49.503906" y="115.503906"/>
<use xlink:href="#glyph0-9" x="52.837891" y="115.503906"/>
<use xlink:href="#glyph0-5" x="60.455078" y="115.503906"/>
<use xlink:href="#glyph0-9" x="64.269531" y="115.503906"/>
<use xlink:href="#glyph0-8" x="71.886719" y="115.503906"/>
<use xlink:href="#glyph0-10" x="75.220703" y="115.503906"/>
<use xlink:href="#glyph0-8" x="82.322266" y="115.503906"/>
<use xlink:href="#glyph0-11" x="85.65625" y="115.503906"/>
<use xlink:href="#glyph0-8" x="91.908203" y="115.503906"/>
<use xlink:href="#glyph0-12" x="95.242188" y="115.503906"/>
<use xlink:href="#glyph0-13" x="102.583984" y="115.503906"/>
<use xlink:href="#glyph0-14" x="110.189453" y="115.503906"/>
<use xlink:href="#glyph0-5" x="114.232422" y="115.503906"/>
<use xlink:href="#glyph0-15" x="118.046875" y="115.503906"/>
<use xlink:href="#glyph0-16" x="125.681641" y="115.503906"/>
<use xlink:href="#glyph0-5" x="133.316406" y="115.503906"/>
<use xlink:href="#glyph0-17" x="137.130859" y="115.503906"/>
<use xlink:href="#glyph0-11" x="144.736328" y="115.503906"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 10.984375 46 L 19.375 46 L 19.375 60 L 10.984375 60 Z M 10.984375 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 19.371094 46 L 117.976562 46 L 117.976562 60 L 19.371094 60 Z M 19.371094 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 117.976562 46 L 657.960938 46 L 657.960938 60 L 117.976562 60 Z M 117.976562 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 657.960938 46 L 690.640625 46 L 690.640625 60 L 657.960938 60 Z M 657.960938 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 690.644531 46 L 762.277344 46 L 762.277344 60 L 690.644531 60 Z M 690.644531 46 "/>
<g clip-path="url(#clip1)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="692.644531" y="58"/>
<use xlink:href="#glyph0-7" x="697.349609" y="58"/>
<use xlink:href="#glyph0-2" x="702.283203" y="58"/>
<use xlink:href="#glyph0-3" x="709.636719" y="58"/>
<use xlink:href="#glyph0-19" x="716.234375" y="58"/>
<use xlink:href="#glyph0-20" x="723.617188" y="58"/>
<use xlink:href="#glyph0-13" x="726.951172" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 762.277344 46 L 765.703125 46 L 765.703125 60 L 762.277344 60 Z M 762.277344 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 765.699219 46 L 874.28125 46 L 874.28125 60 L 765.699219 60 Z M 765.699219 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 874.285156 46 L 883.320312 46 L 883.320312 60 L 874.285156 60 Z M 874.285156 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 883.316406 46 L 950.152344 46 L 950.152344 60 L 883.316406 60 Z M 883.316406 46 "/>
<g clip-path="url(#clip2)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="885.316406" y="58"/>
<use xlink:href="#glyph0-7" x="890.021484" y="58"/>
<use xlink:href="#glyph0-2" x="894.955078" y="58"/>
<use xlink:href="#glyph0-3" x="902.308594" y="58"/>
<use xlink:href="#glyph0-19" x="908.90625" y="58"/>
<use xlink:href="#glyph0-20" x="916.289062" y="58"/>
<use xlink:href="#glyph0-13" x="919.623047" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 950.152344 46 L 952.253906 46 L 952.253906 60 L 950.152344 60 Z M 950.152344 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 952.253906 46 L 1010.515625 46 L 1010.515625 60 L 952.253906 60 Z M 952.253906 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1010.515625 46 L 1036.214844 46 L 1036.214844 60 L 1010.515625 60 Z M 1010.515625 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1036.214844 46 L 1060.753906 46 L 1060.753906 60 L 1036.214844 60 Z M 1036.214844 46 "/>
<g clip-path="url(#clip3)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="1038.214844" y="58"/>
<use xlink:href="#glyph0-7" x="1042.919922" y="58"/>
<use xlink:href="#glyph0-2" x="1047.853516" y="58"/>
<use xlink:href="#glyph0-3" x="1055.207031" y="58"/>
<use xlink:href="#glyph0-19" x="1061.804688" y="58"/>
<use xlink:href="#glyph0-20" x="1069.1875" y="58"/>
<use xlink:href="#glyph0-13" x="1072.521484" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1060.753906 46 L 1062.9375 46 L 1062.9375 60 L 1060.753906 60 Z M 1060.753906 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1062.9375 46 L 1096.113281 46 L 1096.113281 60 L 1062.9375 60 Z M 1062.9375 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1096.117188 46 L 1185.753906 46 L 1185.753906 60 L 1096.117188 60 Z M 1096.117188 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1185.75 46 L 1231.652344 46 L 1231.652344 60 L 1185.75 60 Z M 1185.75 46 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-3" x="1187.75" y="58"/>
<use xlink:href="#glyph0-20" x="1194.347656" y="58"/>
<use xlink:href="#glyph0-12" x="1197.681641" y="58"/>
<use xlink:href="#glyph0-11" x="1205.023438" y="58"/>
<use xlink:href="#glyph0-19" x="1211.275391" y="58"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1231.652344 46 L 1108.410156 46 L 1108.410156 60 L 1231.652344 60 Z M 1231.652344 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 117.976562 78 L 125.953125 78 L 125.953125 92 L 117.976562 92 Z M 117.976562 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 125.953125 78 L 273.785156 78 L 273.785156 92 L 125.953125 92 Z M 125.953125 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 273.785156 78 L 630.226562 78 L 630.226562 92 L 273.785156 92 Z M 273.785156 78 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="275.785156" y="90"/>
<use xlink:href="#glyph0-7" x="280.490234" y="90"/>
<use xlink:href="#glyph0-2" x="285.423828" y="90"/>
<use xlink:href="#glyph0-3" x="292.777344" y="90"/>
<use xlink:href="#glyph0-19" x="299.375" y="90"/>
<use xlink:href="#glyph0-20" x="306.757812" y="90"/>
<use xlink:href="#glyph0-13" x="310.091797" y="90"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 630.230469 78 L 643.714844 78 L 643.714844 92 L 630.230469 92 Z M 630.230469 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 643.714844 78 L 803.261719 78 L 803.261719 92 L 643.714844 92 Z M 643.714844 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 803.261719 78 L 817.328125 78 L 817.328125 92 L 803.261719 92 Z M 803.261719 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 817.328125 78 L 869.75 78 L 869.75 92 L 817.328125 92 Z M 817.328125 78 "/>
<g clip-path="url(#clip4)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="819.328125" y="90"/>
<use xlink:href="#glyph0-7" x="824.033203" y="90"/>
<use xlink:href="#glyph0-2" x="828.966797" y="90"/>
<use xlink:href="#glyph0-3" x="836.320312" y="90"/>
<use xlink:href="#glyph0-19" x="842.917969" y="90"/>
<use xlink:href="#glyph0-20" x="850.300781" y="90"/>
<use xlink:href="#glyph0-13" x="853.634766" y="90"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 869.75 78 L 871.902344 78 L 871.902344 92 L 869.75 92 Z M 869.75 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 871.902344 78 L 956.558594 78 L 956.558594 92 L 871.902344 92 Z M 871.902344 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 956.554688 78 L 964.960938 78 L 964.960938 92 L 956.554688 92 Z M 956.554688 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 964.960938 78 L 1006.179688 78 L 1006.179688 92 L 964.960938 92 Z M 964.960938 78 "/>
<g clip-path="url(#clip5)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="966.960938" y="90"/>
<use xlink:href="#glyph0-7" x="971.666016" y="90"/>
<use xlink:href="#glyph0-2" x="976.599609" y="90"/>
<use xlink:href="#glyph0-3" x="983.953125" y="90"/>
<use xlink:href="#glyph0-19" x="990.550781" y="90"/>
<use xlink:href="#glyph0-20" x="997.933594" y="90"/>
<use xlink:href="#glyph0-13" x="1001.267578" y="90"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1006.179688 78 L 1008.5625 78 L 1008.5625 92 L 1006.179688 92 Z M 1006.179688 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1008.5625 78 L 1083.6875 78 L 1083.6875 92 L 1008.5625 92 Z M 1008.5625 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1083.6875 78 L 1096.113281 78 L 1096.113281 92 L 1083.6875 92 Z M 1083.6875 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1096.117188 78 L 1093.667969 78 L 1093.667969 92 L 1096.117188 92 Z M 1096.117188 78 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 431.214844 81 L 431.214844 75 "/>
<g clip-path="url(#clip6)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="433.214844" y="76"/>
<use xlink:href="#glyph1-2" x="437.949219" y="76"/>
<use xlink:href="#glyph1-3" x="440.492188" y="76"/>
<use xlink:href="#glyph1-2" x="447.195312" y="76"/>
<use xlink:href="#glyph1-4" x="449.738281" y="76"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 852.011719 81 L 852.011719 75 "/>
<g clip-path="url(#clip7)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="854.011719" y="76"/>
<use xlink:href="#glyph1-2" x="858.746094" y="76"/>
<use xlink:href="#glyph1-3" x="861.289062" y="76"/>
<use xlink:href="#glyph1-2" x="867.992188" y="76"/>
<use xlink:href="#glyph1-5" x="870.535156" y="76"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 991.652344 81 L 991.652344 75 "/>
<g clip-path="url(#clip8)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="993.652344" y="76"/>
<use xlink:href="#glyph1-2" x="998.386719" y="76"/>
<use xlink:href="#glyph1-3" x="1000.929688" y="76"/>
<use xlink:href="#glyph1-2" x="1007.632812" y="76"/>
<use xlink:href="#glyph1-6" x="1010.175781" y="76"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 117.976562 46 L 117.976562 92 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 736.246094 49 L 736.246094 43 "/>
<g clip-path="url(#clip9)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-7" x="738.246094" y="44"/>
<use xlink:href="#glyph1-2" x="742.980469" y="44"/>
<use xlink:href="#glyph1-3" x="745.523438" y="44"/>
<use xlink:href="#glyph1-2" x="752.226562" y="44"/>
<use xlink:href="#glyph1-4" x="754.769531" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 935.078125 49 L 935.078125 43 "/>
<g clip-path="url(#clip10)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-7" x="937.078125" y="44"/>
<use xlink:href="#glyph1-2" x="941.8125" y="44"/>
<use xlink:href="#glyph1-3" x="944.355469" y="44"/>
<use xlink:href="#glyph1-2" x="951.058594" y="44"/>
<use xlink:href="#glyph1-5" x="953.601562" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1045.515625 49 L 1045.515625 43 "/>
<g clip-path="url(#clip11)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-7" x="1047.515625" y="44"/>
<use xlink:href="#glyph1-2" x="1052.25" y="44"/>
<use xlink:href="#glyph1-3" x="1054.792969" y="44"/>
<use xlink:href="#glyph1-2" x="1061.496094" y="44"/>
<use xlink:href="#glyph1-6" x="1064.039062" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 78.671875 36 L 74.671875 36 L 74.671875 96 L 78.671875 96 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1101.464844 36 L 1105.464844 36 L 1105.464844 96 L 1101.464844 96 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-8" x="76.671875" y="44"/>
<use xlink:href="#glyph1-9" x="81.75" y="44"/>
<use xlink:href="#glyph1-10" x="86.644531" y="44"/>
<use xlink:href="#glyph1-11" x="89.78125" y="44"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 10.984375 46 L 10.984375 60 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 40 KiB

BIN
doc/traces/cancel-posix.fxt Normal file

Binary file not shown.

344
doc/traces/cancel-posix.svg Normal file
View File

@ -0,0 +1,344 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1280pt" height="120pt" viewBox="0 0 1280 120" version="1.1">
<defs>
<g>
<symbol overflow="visible" id="glyph0-0">
<path style="stroke:none;" d="M 0.59375 2.125 L 0.59375 -8.46875 L 6.59375 -8.46875 L 6.59375 2.125 Z M 1.265625 1.453125 L 5.9375 1.453125 L 5.9375 -7.78125 L 1.265625 -7.78125 Z M 1.265625 1.453125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-1">
<path style="stroke:none;" d="M 1.171875 -8.75 L 6.703125 -8.75 L 6.703125 -7.75 L 2.359375 -7.75 L 2.359375 -5.15625 L 6.53125 -5.15625 L 6.53125 -4.171875 L 2.359375 -4.171875 L 2.359375 -1 L 6.8125 -1 L 6.8125 0 L 1.171875 0 Z M 1.171875 -8.75 "/>
</symbol>
<symbol overflow="visible" id="glyph0-2">
<path style="stroke:none;" d="M 4.109375 -3.296875 C 3.242188 -3.296875 2.640625 -3.195312 2.296875 -3 C 1.960938 -2.800781 1.796875 -2.460938 1.796875 -1.984375 C 1.796875 -1.597656 1.921875 -1.289062 2.171875 -1.0625 C 2.429688 -0.84375 2.773438 -0.734375 3.203125 -0.734375 C 3.804688 -0.734375 4.285156 -0.941406 4.640625 -1.359375 C 5.003906 -1.785156 5.1875 -2.351562 5.1875 -3.0625 L 5.1875 -3.296875 Z M 6.265625 -3.75 L 6.265625 0 L 5.1875 0 L 5.1875 -1 C 4.9375 -0.601562 4.628906 -0.304688 4.265625 -0.109375 C 3.898438 0.078125 3.453125 0.171875 2.921875 0.171875 C 2.242188 0.171875 1.707031 -0.015625 1.3125 -0.390625 C 0.914062 -0.773438 0.71875 -1.28125 0.71875 -1.90625 C 0.71875 -2.644531 0.960938 -3.203125 1.453125 -3.578125 C 1.953125 -3.953125 2.691406 -4.140625 3.671875 -4.140625 L 5.1875 -4.140625 L 5.1875 -4.25 C 5.1875 -4.75 5.019531 -5.132812 4.6875 -5.40625 C 4.363281 -5.675781 3.910156 -5.8125 3.328125 -5.8125 C 2.953125 -5.8125 2.582031 -5.765625 2.21875 -5.671875 C 1.863281 -5.578125 1.523438 -5.441406 1.203125 -5.265625 L 1.203125 -6.265625 C 1.597656 -6.421875 1.976562 -6.535156 2.34375 -6.609375 C 2.71875 -6.679688 3.082031 -6.71875 3.4375 -6.71875 C 4.382812 -6.71875 5.09375 -6.472656 5.5625 -5.984375 C 6.03125 -5.492188 6.265625 -4.75 6.265625 -3.75 Z M 6.265625 -3.75 "/>
</symbol>
<symbol overflow="visible" id="glyph0-3">
<path style="stroke:none;" d="M 5.859375 -6.3125 L 5.859375 -5.296875 C 5.546875 -5.472656 5.238281 -5.601562 4.9375 -5.6875 C 4.632812 -5.769531 4.328125 -5.8125 4.015625 -5.8125 C 3.304688 -5.8125 2.757812 -5.585938 2.375 -5.140625 C 1.988281 -4.703125 1.796875 -4.082031 1.796875 -3.28125 C 1.796875 -2.476562 1.988281 -1.851562 2.375 -1.40625 C 2.757812 -0.96875 3.304688 -0.75 4.015625 -0.75 C 4.328125 -0.75 4.632812 -0.789062 4.9375 -0.875 C 5.238281 -0.957031 5.546875 -1.082031 5.859375 -1.25 L 5.859375 -0.25 C 5.554688 -0.113281 5.242188 -0.0078125 4.921875 0.0625 C 4.597656 0.132812 4.253906 0.171875 3.890625 0.171875 C 2.898438 0.171875 2.113281 -0.132812 1.53125 -0.75 C 0.945312 -1.375 0.65625 -2.21875 0.65625 -3.28125 C 0.65625 -4.34375 0.945312 -5.179688 1.53125 -5.796875 C 2.125 -6.410156 2.9375 -6.71875 3.96875 -6.71875 C 4.289062 -6.71875 4.609375 -6.679688 4.921875 -6.609375 C 5.242188 -6.546875 5.554688 -6.445312 5.859375 -6.3125 Z M 5.859375 -6.3125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-4">
<path style="stroke:none;" d="M 6.59375 -3.96875 L 6.59375 0 L 5.515625 0 L 5.515625 -3.921875 C 5.515625 -4.546875 5.390625 -5.007812 5.140625 -5.3125 C 4.898438 -5.625 4.539062 -5.78125 4.0625 -5.78125 C 3.476562 -5.78125 3.015625 -5.59375 2.671875 -5.21875 C 2.335938 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -9.125 L 2.171875 -9.125 L 2.171875 -5.546875 C 2.429688 -5.941406 2.734375 -6.234375 3.078125 -6.421875 C 3.429688 -6.617188 3.835938 -6.71875 4.296875 -6.71875 C 5.046875 -6.71875 5.613281 -6.484375 6 -6.015625 C 6.394531 -5.554688 6.59375 -4.875 6.59375 -3.96875 Z M 6.59375 -3.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-5">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph0-6">
<path style="stroke:none;" d="M 5.453125 -3.359375 C 5.453125 -4.140625 5.289062 -4.742188 4.96875 -5.171875 C 4.644531 -5.597656 4.191406 -5.8125 3.609375 -5.8125 C 3.035156 -5.8125 2.585938 -5.597656 2.265625 -5.171875 C 1.941406 -4.742188 1.78125 -4.140625 1.78125 -3.359375 C 1.78125 -2.578125 1.941406 -1.972656 2.265625 -1.546875 C 2.585938 -1.117188 3.035156 -0.90625 3.609375 -0.90625 C 4.191406 -0.90625 4.644531 -1.117188 4.96875 -1.546875 C 5.289062 -1.972656 5.453125 -2.578125 5.453125 -3.359375 Z M 6.53125 -0.8125 C 6.53125 0.300781 6.28125 1.128906 5.78125 1.671875 C 5.289062 2.222656 4.53125 2.5 3.5 2.5 C 3.125 2.5 2.765625 2.46875 2.421875 2.40625 C 2.085938 2.351562 1.765625 2.269531 1.453125 2.15625 L 1.453125 1.109375 C 1.765625 1.273438 2.078125 1.398438 2.390625 1.484375 C 2.703125 1.566406 3.015625 1.609375 3.328125 1.609375 C 4.035156 1.609375 4.566406 1.421875 4.921875 1.046875 C 5.273438 0.679688 5.453125 0.125 5.453125 -0.625 L 5.453125 -1.15625 C 5.222656 -0.769531 4.9375 -0.476562 4.59375 -0.28125 C 4.25 -0.09375 3.832031 0 3.34375 0 C 2.539062 0 1.890625 -0.304688 1.390625 -0.921875 C 0.898438 -1.535156 0.65625 -2.347656 0.65625 -3.359375 C 0.65625 -4.367188 0.898438 -5.179688 1.390625 -5.796875 C 1.890625 -6.410156 2.539062 -6.71875 3.34375 -6.71875 C 3.832031 -6.71875 4.25 -6.617188 4.59375 -6.421875 C 4.9375 -6.234375 5.222656 -5.945312 5.453125 -5.5625 L 5.453125 -6.5625 L 6.53125 -6.5625 Z M 6.53125 -0.8125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-7">
<path style="stroke:none;" d="M 4.9375 -5.5625 C 4.8125 -5.625 4.675781 -5.671875 4.53125 -5.703125 C 4.394531 -5.742188 4.238281 -5.765625 4.0625 -5.765625 C 3.457031 -5.765625 2.988281 -5.566406 2.65625 -5.171875 C 2.332031 -4.773438 2.171875 -4.203125 2.171875 -3.453125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.398438 -5.941406 2.695312 -6.234375 3.0625 -6.421875 C 3.425781 -6.617188 3.867188 -6.71875 4.390625 -6.71875 C 4.460938 -6.71875 4.539062 -6.710938 4.625 -6.703125 C 4.71875 -6.691406 4.816406 -6.675781 4.921875 -6.65625 Z M 4.9375 -5.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-8">
<path style="stroke:none;" d="M 1.125 -6.5625 L 2.203125 -6.5625 L 2.203125 0 L 1.125 0 Z M 1.125 -9.125 L 2.203125 -9.125 L 2.203125 -7.75 L 1.125 -7.75 Z M 1.125 -9.125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-9">
<path style="stroke:none;" d="M 5.453125 -5.5625 L 5.453125 -9.125 L 6.53125 -9.125 L 6.53125 0 L 5.453125 0 L 5.453125 -0.984375 C 5.222656 -0.597656 4.9375 -0.304688 4.59375 -0.109375 C 4.25 0.078125 3.832031 0.171875 3.34375 0.171875 C 2.550781 0.171875 1.90625 -0.144531 1.40625 -0.78125 C 0.90625 -1.414062 0.65625 -2.25 0.65625 -3.28125 C 0.65625 -4.3125 0.90625 -5.140625 1.40625 -5.765625 C 1.90625 -6.398438 2.550781 -6.71875 3.34375 -6.71875 C 3.832031 -6.71875 4.25 -6.625 4.59375 -6.4375 C 4.9375 -6.25 5.222656 -5.957031 5.453125 -5.5625 Z M 1.78125 -3.28125 C 1.78125 -2.488281 1.941406 -1.863281 2.265625 -1.40625 C 2.585938 -0.957031 3.035156 -0.734375 3.609375 -0.734375 C 4.179688 -0.734375 4.628906 -0.957031 4.953125 -1.40625 C 5.285156 -1.863281 5.453125 -2.488281 5.453125 -3.28125 C 5.453125 -4.070312 5.285156 -4.691406 4.953125 -5.140625 C 4.628906 -5.585938 4.179688 -5.8125 3.609375 -5.8125 C 3.035156 -5.8125 2.585938 -5.585938 2.265625 -5.140625 C 1.941406 -4.691406 1.78125 -4.070312 1.78125 -3.28125 Z M 1.78125 -3.28125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-10">
<path style="stroke:none;" d="M 0.359375 -6.5625 L 1.5 -6.5625 L 3.546875 -1.0625 L 5.609375 -6.5625 L 6.75 -6.5625 L 4.28125 0 L 2.8125 0 Z M 0.359375 -6.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-11">
<path style="stroke:none;" d="M 5.3125 -6.375 L 5.3125 -5.34375 C 5.007812 -5.5 4.691406 -5.613281 4.359375 -5.6875 C 4.035156 -5.769531 3.695312 -5.8125 3.34375 -5.8125 C 2.8125 -5.8125 2.410156 -5.726562 2.140625 -5.5625 C 1.867188 -5.40625 1.734375 -5.160156 1.734375 -4.828125 C 1.734375 -4.578125 1.828125 -4.378906 2.015625 -4.234375 C 2.210938 -4.097656 2.601562 -3.96875 3.1875 -3.84375 L 3.546875 -3.75 C 4.316406 -3.59375 4.863281 -3.363281 5.1875 -3.0625 C 5.507812 -2.757812 5.671875 -2.34375 5.671875 -1.8125 C 5.671875 -1.195312 5.425781 -0.710938 4.9375 -0.359375 C 4.457031 -0.00390625 3.796875 0.171875 2.953125 0.171875 C 2.597656 0.171875 2.226562 0.132812 1.84375 0.0625 C 1.46875 0 1.070312 -0.0976562 0.65625 -0.234375 L 0.65625 -1.359375 C 1.050781 -1.148438 1.441406 -0.992188 1.828125 -0.890625 C 2.210938 -0.785156 2.597656 -0.734375 2.984375 -0.734375 C 3.484375 -0.734375 3.867188 -0.816406 4.140625 -0.984375 C 4.421875 -1.160156 4.5625 -1.410156 4.5625 -1.734375 C 4.5625 -2.023438 4.460938 -2.25 4.265625 -2.40625 C 4.066406 -2.5625 3.632812 -2.710938 2.96875 -2.859375 L 2.59375 -2.9375 C 1.925781 -3.082031 1.441406 -3.300781 1.140625 -3.59375 C 0.847656 -3.882812 0.703125 -4.28125 0.703125 -4.78125 C 0.703125 -5.40625 0.921875 -5.882812 1.359375 -6.21875 C 1.796875 -6.550781 2.414062 -6.71875 3.21875 -6.71875 C 3.613281 -6.71875 3.988281 -6.6875 4.34375 -6.625 C 4.695312 -6.570312 5.019531 -6.488281 5.3125 -6.375 Z M 5.3125 -6.375 "/>
</symbol>
<symbol overflow="visible" id="glyph0-12">
<path style="stroke:none;" d="M 3.671875 -5.8125 C 3.097656 -5.8125 2.640625 -5.582031 2.296875 -5.125 C 1.960938 -4.675781 1.796875 -4.0625 1.796875 -3.28125 C 1.796875 -2.488281 1.960938 -1.867188 2.296875 -1.421875 C 2.628906 -0.972656 3.085938 -0.75 3.671875 -0.75 C 4.242188 -0.75 4.695312 -0.972656 5.03125 -1.421875 C 5.375 -1.878906 5.546875 -2.5 5.546875 -3.28125 C 5.546875 -4.050781 5.375 -4.664062 5.03125 -5.125 C 4.695312 -5.582031 4.242188 -5.8125 3.671875 -5.8125 Z M 3.671875 -6.71875 C 4.609375 -6.71875 5.34375 -6.410156 5.875 -5.796875 C 6.414062 -5.191406 6.6875 -4.351562 6.6875 -3.28125 C 6.6875 -2.207031 6.414062 -1.363281 5.875 -0.75 C 5.34375 -0.132812 4.609375 0.171875 3.671875 0.171875 C 2.734375 0.171875 1.992188 -0.132812 1.453125 -0.75 C 0.921875 -1.363281 0.65625 -2.207031 0.65625 -3.28125 C 0.65625 -4.351562 0.921875 -5.191406 1.453125 -5.796875 C 1.992188 -6.410156 2.734375 -6.71875 3.671875 -6.71875 Z M 3.671875 -6.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-13">
<path style="stroke:none;" d="M 6.59375 -3.96875 L 6.59375 0 L 5.515625 0 L 5.515625 -3.921875 C 5.515625 -4.546875 5.390625 -5.007812 5.140625 -5.3125 C 4.898438 -5.625 4.539062 -5.78125 4.0625 -5.78125 C 3.476562 -5.78125 3.015625 -5.59375 2.671875 -5.21875 C 2.335938 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.429688 -5.941406 2.734375 -6.234375 3.078125 -6.421875 C 3.429688 -6.617188 3.835938 -6.71875 4.296875 -6.71875 C 5.046875 -6.71875 5.613281 -6.484375 6 -6.015625 C 6.394531 -5.554688 6.59375 -4.875 6.59375 -3.96875 Z M 6.59375 -3.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-14">
<path style="stroke:none;" d="M 1.40625 -1.484375 L 2.640625 -1.484375 L 2.640625 0 L 1.40625 0 Z M 1.40625 -6.203125 L 2.640625 -6.203125 L 2.640625 -4.71875 L 1.40625 -4.71875 Z M 1.40625 -6.203125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-15">
<path style="stroke:none;" d="M 1.484375 -1 L 3.421875 -1 L 3.421875 -7.671875 L 1.3125 -7.25 L 1.3125 -8.328125 L 3.40625 -8.75 L 4.59375 -8.75 L 4.59375 -1 L 6.53125 -1 L 6.53125 0 L 1.484375 0 Z M 1.484375 -1 "/>
</symbol>
<symbol overflow="visible" id="glyph0-16">
<path style="stroke:none;" d="M 3.8125 -7.96875 C 3.207031 -7.96875 2.75 -7.664062 2.4375 -7.0625 C 2.132812 -6.46875 1.984375 -5.566406 1.984375 -4.359375 C 1.984375 -3.160156 2.132812 -2.257812 2.4375 -1.65625 C 2.75 -1.0625 3.207031 -0.765625 3.8125 -0.765625 C 4.425781 -0.765625 4.882812 -1.0625 5.1875 -1.65625 C 5.5 -2.257812 5.65625 -3.160156 5.65625 -4.359375 C 5.65625 -5.566406 5.5 -6.46875 5.1875 -7.0625 C 4.882812 -7.664062 4.425781 -7.96875 3.8125 -7.96875 Z M 3.8125 -8.90625 C 4.789062 -8.90625 5.539062 -8.515625 6.0625 -7.734375 C 6.582031 -6.960938 6.84375 -5.835938 6.84375 -4.359375 C 6.84375 -2.890625 6.582031 -1.765625 6.0625 -0.984375 C 5.539062 -0.210938 4.789062 0.171875 3.8125 0.171875 C 2.832031 0.171875 2.082031 -0.210938 1.5625 -0.984375 C 1.050781 -1.765625 0.796875 -2.890625 0.796875 -4.359375 C 0.796875 -5.835938 1.050781 -6.960938 1.5625 -7.734375 C 2.082031 -8.515625 2.832031 -8.90625 3.8125 -8.90625 Z M 3.8125 -8.90625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-17">
<path style="stroke:none;" d="M 1.015625 -2.59375 L 1.015625 -6.5625 L 2.09375 -6.5625 L 2.09375 -2.625 C 2.09375 -2.007812 2.210938 -1.546875 2.453125 -1.234375 C 2.703125 -0.921875 3.066406 -0.765625 3.546875 -0.765625 C 4.128906 -0.765625 4.585938 -0.945312 4.921875 -1.3125 C 5.265625 -1.6875 5.4375 -2.195312 5.4375 -2.84375 L 5.4375 -6.5625 L 6.515625 -6.5625 L 6.515625 0 L 5.4375 0 L 5.4375 -1.015625 C 5.175781 -0.609375 4.875 -0.304688 4.53125 -0.109375 C 4.1875 0.078125 3.785156 0.171875 3.328125 0.171875 C 2.566406 0.171875 1.988281 -0.0625 1.59375 -0.53125 C 1.207031 -1 1.015625 -1.6875 1.015625 -2.59375 Z M 3.734375 -6.71875 Z M 3.734375 -6.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-18">
<path style="stroke:none;" d="M 2.171875 -0.984375 L 2.171875 2.5 L 1.09375 2.5 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.5625 C 2.398438 -5.957031 2.6875 -6.25 3.03125 -6.4375 C 3.375 -6.625 3.785156 -6.71875 4.265625 -6.71875 C 5.066406 -6.71875 5.71875 -6.398438 6.21875 -5.765625 C 6.71875 -5.140625 6.96875 -4.3125 6.96875 -3.28125 C 6.96875 -2.25 6.71875 -1.414062 6.21875 -0.78125 C 5.71875 -0.144531 5.066406 0.171875 4.265625 0.171875 C 3.785156 0.171875 3.375 0.078125 3.03125 -0.109375 C 2.6875 -0.304688 2.398438 -0.597656 2.171875 -0.984375 Z M 5.84375 -3.28125 C 5.84375 -4.070312 5.675781 -4.691406 5.34375 -5.140625 C 5.019531 -5.585938 4.578125 -5.8125 4.015625 -5.8125 C 3.441406 -5.8125 2.988281 -5.585938 2.65625 -5.140625 C 2.332031 -4.691406 2.171875 -4.070312 2.171875 -3.28125 C 2.171875 -2.488281 2.332031 -1.863281 2.65625 -1.40625 C 2.988281 -0.957031 3.441406 -0.734375 4.015625 -0.734375 C 4.578125 -0.734375 5.019531 -0.957031 5.34375 -1.40625 C 5.675781 -1.863281 5.84375 -2.488281 5.84375 -3.28125 Z M 5.84375 -3.28125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-19">
<path style="stroke:none;" d="M 6.75 -3.546875 L 6.75 -3.03125 L 1.78125 -3.03125 C 1.832031 -2.28125 2.054688 -1.710938 2.453125 -1.328125 C 2.859375 -0.941406 3.414062 -0.75 4.125 -0.75 C 4.539062 -0.75 4.941406 -0.796875 5.328125 -0.890625 C 5.722656 -0.992188 6.113281 -1.148438 6.5 -1.359375 L 6.5 -0.328125 C 6.101562 -0.171875 5.703125 -0.0507812 5.296875 0.03125 C 4.890625 0.125 4.476562 0.171875 4.0625 0.171875 C 3.019531 0.171875 2.191406 -0.128906 1.578125 -0.734375 C 0.960938 -1.347656 0.65625 -2.175781 0.65625 -3.21875 C 0.65625 -4.289062 0.945312 -5.140625 1.53125 -5.765625 C 2.113281 -6.398438 2.894531 -6.71875 3.875 -6.71875 C 4.757812 -6.71875 5.457031 -6.429688 5.96875 -5.859375 C 6.488281 -5.296875 6.75 -4.523438 6.75 -3.546875 Z M 5.671875 -3.875 C 5.660156 -4.457031 5.492188 -4.925781 5.171875 -5.28125 C 4.847656 -5.632812 4.421875 -5.8125 3.890625 -5.8125 C 3.285156 -5.8125 2.800781 -5.640625 2.4375 -5.296875 C 2.082031 -4.953125 1.878906 -4.472656 1.828125 -3.859375 Z M 5.671875 -3.875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-20">
<path style="stroke:none;" d="M 0.59375 -3.765625 L 3.75 -3.765625 L 3.75 -2.8125 L 0.59375 -2.8125 Z M 0.59375 -3.765625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-21">
<path style="stroke:none;" d="M 6.234375 -5.296875 C 6.503906 -5.785156 6.828125 -6.144531 7.203125 -6.375 C 7.578125 -6.601562 8.019531 -6.71875 8.53125 -6.71875 C 9.21875 -6.71875 9.742188 -6.476562 10.109375 -6 C 10.484375 -5.519531 10.671875 -4.84375 10.671875 -3.96875 L 10.671875 0 L 9.59375 0 L 9.59375 -3.921875 C 9.59375 -4.554688 9.476562 -5.023438 9.25 -5.328125 C 9.03125 -5.628906 8.691406 -5.78125 8.234375 -5.78125 C 7.671875 -5.78125 7.226562 -5.59375 6.90625 -5.21875 C 6.582031 -4.851562 6.421875 -4.347656 6.421875 -3.703125 L 6.421875 0 L 5.34375 0 L 5.34375 -3.921875 C 5.34375 -4.554688 5.226562 -5.023438 5 -5.328125 C 4.78125 -5.628906 4.4375 -5.78125 3.96875 -5.78125 C 3.414062 -5.78125 2.976562 -5.59375 2.65625 -5.21875 C 2.332031 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.421875 -5.941406 2.71875 -6.234375 3.0625 -6.421875 C 3.40625 -6.617188 3.8125 -6.71875 4.28125 -6.71875 C 4.757812 -6.71875 5.164062 -6.597656 5.5 -6.359375 C 5.832031 -6.117188 6.078125 -5.765625 6.234375 -5.296875 Z M 6.234375 -5.296875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-22">
<path style="stroke:none;" d="M 6.421875 -8.46875 L 6.421875 -7.3125 C 5.972656 -7.519531 5.546875 -7.675781 5.140625 -7.78125 C 4.742188 -7.894531 4.363281 -7.953125 4 -7.953125 C 3.351562 -7.953125 2.851562 -7.828125 2.5 -7.578125 C 2.15625 -7.328125 1.984375 -6.96875 1.984375 -6.5 C 1.984375 -6.113281 2.097656 -5.820312 2.328125 -5.625 C 2.554688 -5.425781 3 -5.269531 3.65625 -5.15625 L 4.359375 -5 C 5.242188 -4.832031 5.894531 -4.535156 6.3125 -4.109375 C 6.738281 -3.691406 6.953125 -3.128906 6.953125 -2.421875 C 6.953125 -1.566406 6.664062 -0.921875 6.09375 -0.484375 C 5.53125 -0.046875 4.695312 0.171875 3.59375 0.171875 C 3.1875 0.171875 2.75 0.125 2.28125 0.03125 C 1.8125 -0.0625 1.328125 -0.203125 0.828125 -0.390625 L 0.828125 -1.609375 C 1.304688 -1.335938 1.773438 -1.132812 2.234375 -1 C 2.703125 -0.863281 3.15625 -0.796875 3.59375 -0.796875 C 4.269531 -0.796875 4.789062 -0.925781 5.15625 -1.1875 C 5.53125 -1.457031 5.71875 -1.835938 5.71875 -2.328125 C 5.71875 -2.753906 5.582031 -3.085938 5.3125 -3.328125 C 5.050781 -3.578125 4.617188 -3.757812 4.015625 -3.875 L 3.296875 -4.015625 C 2.410156 -4.191406 1.769531 -4.46875 1.375 -4.84375 C 0.988281 -5.21875 0.796875 -5.738281 0.796875 -6.40625 C 0.796875 -7.1875 1.066406 -7.796875 1.609375 -8.234375 C 2.148438 -8.679688 2.898438 -8.90625 3.859375 -8.90625 C 4.273438 -8.90625 4.691406 -8.867188 5.109375 -8.796875 C 5.535156 -8.722656 5.972656 -8.613281 6.421875 -8.46875 Z M 6.421875 -8.46875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-23">
<path style="stroke:none;" d="M 0.5 -6.5625 L 1.578125 -6.5625 L 2.9375 -1.4375 L 4.265625 -6.5625 L 5.546875 -6.5625 L 6.890625 -1.4375 L 8.234375 -6.5625 L 9.3125 -6.5625 L 7.59375 0 L 6.328125 0 L 4.90625 -5.375 L 3.5 0 L 2.21875 0 Z M 0.5 -6.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-24">
<path style="stroke:none;" d="M 2.203125 -8.421875 L 2.203125 -6.5625 L 4.421875 -6.5625 L 4.421875 -5.71875 L 2.203125 -5.71875 L 2.203125 -2.15625 C 2.203125 -1.625 2.273438 -1.28125 2.421875 -1.125 C 2.566406 -0.976562 2.863281 -0.90625 3.3125 -0.90625 L 4.421875 -0.90625 L 4.421875 0 L 3.3125 0 C 2.476562 0 1.898438 -0.15625 1.578125 -0.46875 C 1.265625 -0.78125 1.109375 -1.34375 1.109375 -2.15625 L 1.109375 -5.71875 L 0.328125 -5.71875 L 0.328125 -6.5625 L 1.109375 -6.5625 L 1.109375 -8.421875 Z M 2.203125 -8.421875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-25">
<path style="stroke:none;" d="M 1.28125 -1.484375 L 2.515625 -1.484375 L 2.515625 0 L 1.28125 0 Z M 1.28125 -1.484375 "/>
</symbol>
<symbol overflow="visible" id="glyph0-26">
<path style="stroke:none;" d="M 6.125 2 L 6.125 2.828125 L -0.125 2.828125 L -0.125 2 Z M 6.125 2 "/>
</symbol>
<symbol overflow="visible" id="glyph0-27">
<path style="stroke:none;" d="M 1.125 -9.125 L 2.203125 -9.125 L 2.203125 0 L 1.125 0 Z M 1.125 -9.125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-0">
<path style="stroke:none;" d="M 0.40625 1.421875 L 0.40625 -5.640625 L 4.40625 -5.640625 L 4.40625 1.421875 Z M 0.84375 0.96875 L 3.953125 0.96875 L 3.953125 -5.1875 L 0.84375 -5.1875 Z M 0.84375 0.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-1">
<path style="stroke:none;" d="M 4.390625 -4.375 L 2.8125 -2.25 L 4.46875 0 L 3.625 0 L 2.359375 -1.71875 L 1.078125 0 L 0.234375 0 L 1.9375 -2.296875 L 0.375 -4.375 L 1.21875 -4.375 L 2.390625 -2.8125 L 3.546875 -4.375 Z M 4.390625 -4.375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-2">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph1-3">
<path style="stroke:none;" d="M 0.84375 -3.640625 L 5.859375 -3.640625 L 5.859375 -2.984375 L 0.84375 -2.984375 Z M 0.84375 -2.046875 L 5.859375 -2.046875 L 5.859375 -1.375 L 0.84375 -1.375 Z M 0.84375 -2.046875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-4">
<path style="stroke:none;" d="M 1 -0.671875 L 2.28125 -0.671875 L 2.28125 -5.109375 L 0.875 -4.828125 L 0.875 -5.546875 L 2.28125 -5.828125 L 3.0625 -5.828125 L 3.0625 -0.671875 L 4.359375 -0.671875 L 4.359375 0 L 1 0 Z M 1 -0.671875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-5">
<path style="stroke:none;" d="M 0.78125 -5.828125 L 4.140625 -5.828125 L 4.140625 -5.171875 L 1.578125 -5.171875 L 1.578125 -3.453125 L 3.890625 -3.453125 L 3.890625 -2.78125 L 1.578125 -2.78125 L 1.578125 0 L 0.78125 0 Z M 0.78125 -5.828125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-6">
<path style="stroke:none;" d="M 2.75 -2.203125 C 2.164062 -2.203125 1.757812 -2.132812 1.53125 -2 C 1.3125 -1.863281 1.203125 -1.640625 1.203125 -1.328125 C 1.203125 -1.066406 1.285156 -0.859375 1.453125 -0.703125 C 1.617188 -0.554688 1.847656 -0.484375 2.140625 -0.484375 C 2.535156 -0.484375 2.851562 -0.625 3.09375 -0.90625 C 3.332031 -1.195312 3.453125 -1.578125 3.453125 -2.046875 L 3.453125 -2.203125 Z M 4.171875 -2.5 L 4.171875 0 L 3.453125 0 L 3.453125 -0.671875 C 3.296875 -0.398438 3.09375 -0.203125 2.84375 -0.078125 C 2.601562 0.046875 2.304688 0.109375 1.953125 0.109375 C 1.503906 0.109375 1.144531 -0.015625 0.875 -0.265625 C 0.613281 -0.515625 0.484375 -0.851562 0.484375 -1.28125 C 0.484375 -1.769531 0.644531 -2.140625 0.96875 -2.390625 C 1.300781 -2.640625 1.796875 -2.765625 2.453125 -2.765625 L 3.453125 -2.765625 L 3.453125 -2.828125 C 3.453125 -3.160156 3.34375 -3.414062 3.125 -3.59375 C 2.914062 -3.78125 2.613281 -3.875 2.21875 -3.875 C 1.96875 -3.875 1.722656 -3.84375 1.484375 -3.78125 C 1.242188 -3.71875 1.015625 -3.628906 0.796875 -3.515625 L 0.796875 -4.171875 C 1.066406 -4.273438 1.320312 -4.351562 1.5625 -4.40625 C 1.8125 -4.457031 2.054688 -4.484375 2.296875 -4.484375 C 2.921875 -4.484375 3.390625 -4.316406 3.703125 -3.984375 C 4.015625 -3.660156 4.171875 -3.164062 4.171875 -2.5 Z M 4.171875 -2.5 "/>
</symbol>
<symbol overflow="visible" id="glyph1-7">
<path style="stroke:none;" d="M 0.75 -4.375 L 1.46875 -4.375 L 1.46875 0 L 0.75 0 Z M 0.75 -6.078125 L 1.46875 -6.078125 L 1.46875 -5.171875 L 0.75 -5.171875 Z M 0.75 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-8">
<path style="stroke:none;" d="M 0.75 -6.078125 L 1.46875 -6.078125 L 1.46875 0 L 0.75 0 Z M 0.75 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-9">
<path style="stroke:none;" d="M 0.6875 -1.734375 L 0.6875 -4.375 L 1.40625 -4.375 L 1.40625 -1.75 C 1.40625 -1.34375 1.484375 -1.035156 1.640625 -0.828125 C 1.804688 -0.617188 2.050781 -0.515625 2.375 -0.515625 C 2.757812 -0.515625 3.0625 -0.632812 3.28125 -0.875 C 3.507812 -1.125 3.625 -1.460938 3.625 -1.890625 L 3.625 -4.375 L 4.34375 -4.375 L 4.34375 0 L 3.625 0 L 3.625 -0.671875 C 3.445312 -0.410156 3.242188 -0.210938 3.015625 -0.078125 C 2.785156 0.046875 2.519531 0.109375 2.21875 0.109375 C 1.71875 0.109375 1.335938 -0.046875 1.078125 -0.359375 C 0.816406 -0.671875 0.6875 -1.128906 0.6875 -1.734375 Z M 2.484375 -4.484375 Z M 2.484375 -4.484375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-10">
<path style="stroke:none;" d="M 3.296875 -3.703125 C 3.210938 -3.753906 3.125 -3.789062 3.03125 -3.8125 C 2.9375 -3.832031 2.832031 -3.84375 2.71875 -3.84375 C 2.3125 -3.84375 2 -3.707031 1.78125 -3.4375 C 1.5625 -3.175781 1.453125 -2.800781 1.453125 -2.3125 L 1.453125 0 L 0.734375 0 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.703125 C 1.597656 -3.960938 1.789062 -4.15625 2.03125 -4.28125 C 2.28125 -4.414062 2.578125 -4.484375 2.921875 -4.484375 C 2.972656 -4.484375 3.023438 -4.476562 3.078125 -4.46875 C 3.140625 -4.46875 3.207031 -4.457031 3.28125 -4.4375 Z M 3.296875 -3.703125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-11">
<path style="stroke:none;" d="M 4.5 -2.375 L 4.5 -2.015625 L 1.1875 -2.015625 C 1.21875 -1.523438 1.367188 -1.148438 1.640625 -0.890625 C 1.910156 -0.628906 2.28125 -0.5 2.75 -0.5 C 3.03125 -0.5 3.300781 -0.53125 3.5625 -0.59375 C 3.820312 -0.664062 4.078125 -0.769531 4.328125 -0.90625 L 4.328125 -0.21875 C 4.066406 -0.113281 3.800781 -0.0351562 3.53125 0.015625 C 3.257812 0.078125 2.988281 0.109375 2.71875 0.109375 C 2.019531 0.109375 1.460938 -0.09375 1.046875 -0.5 C 0.640625 -0.90625 0.4375 -1.453125 0.4375 -2.140625 C 0.4375 -2.859375 0.628906 -3.425781 1.015625 -3.84375 C 1.410156 -4.269531 1.9375 -4.484375 2.59375 -4.484375 C 3.175781 -4.484375 3.640625 -4.289062 3.984375 -3.90625 C 4.328125 -3.53125 4.5 -3.019531 4.5 -2.375 Z M 3.78125 -2.578125 C 3.769531 -2.972656 3.65625 -3.285156 3.4375 -3.515625 C 3.226562 -3.753906 2.945312 -3.875 2.59375 -3.875 C 2.195312 -3.875 1.875 -3.757812 1.625 -3.53125 C 1.382812 -3.300781 1.25 -2.984375 1.21875 -2.578125 Z M 3.78125 -2.578125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-12">
<path style="stroke:none;" d="M 2.484375 -6.078125 C 2.128906 -5.472656 1.867188 -4.878906 1.703125 -4.296875 C 1.535156 -3.710938 1.453125 -3.117188 1.453125 -2.515625 C 1.453125 -1.910156 1.535156 -1.3125 1.703125 -0.71875 C 1.878906 -0.132812 2.140625 0.457031 2.484375 1.0625 L 1.859375 1.0625 C 1.460938 0.445312 1.164062 -0.15625 0.96875 -0.75 C 0.78125 -1.34375 0.6875 -1.929688 0.6875 -2.515625 C 0.6875 -3.097656 0.78125 -3.679688 0.96875 -4.265625 C 1.164062 -4.859375 1.460938 -5.460938 1.859375 -6.078125 Z M 2.484375 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-13">
<path style="stroke:none;" d="M 1.4375 -5.828125 L 1.4375 -3.671875 L 0.765625 -3.671875 L 0.765625 -5.828125 Z M 2.90625 -5.828125 L 2.90625 -3.671875 L 2.25 -3.671875 L 2.25 -5.828125 Z M 2.90625 -5.828125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-14">
<path style="stroke:none;" d="M 4.28125 -5.640625 L 4.28125 -4.875 C 3.976562 -5.019531 3.691406 -5.125 3.421875 -5.1875 C 3.160156 -5.257812 2.910156 -5.296875 2.671875 -5.296875 C 2.234375 -5.296875 1.898438 -5.210938 1.671875 -5.046875 C 1.441406 -4.878906 1.328125 -4.644531 1.328125 -4.34375 C 1.328125 -4.082031 1.398438 -3.882812 1.546875 -3.75 C 1.703125 -3.625 2 -3.519531 2.4375 -3.4375 L 2.90625 -3.34375 C 3.5 -3.226562 3.9375 -3.03125 4.21875 -2.75 C 4.5 -2.46875 4.640625 -2.085938 4.640625 -1.609375 C 4.640625 -1.046875 4.445312 -0.617188 4.0625 -0.328125 C 3.6875 -0.0351562 3.132812 0.109375 2.40625 0.109375 C 2.125 0.109375 1.828125 0.078125 1.515625 0.015625 C 1.203125 -0.046875 0.878906 -0.140625 0.546875 -0.265625 L 0.546875 -1.078125 C 0.867188 -0.890625 1.179688 -0.75 1.484375 -0.65625 C 1.796875 -0.570312 2.101562 -0.53125 2.40625 -0.53125 C 2.851562 -0.53125 3.195312 -0.617188 3.4375 -0.796875 C 3.6875 -0.972656 3.8125 -1.222656 3.8125 -1.546875 C 3.8125 -1.835938 3.722656 -2.0625 3.546875 -2.21875 C 3.367188 -2.382812 3.082031 -2.507812 2.6875 -2.59375 L 2.203125 -2.6875 C 1.609375 -2.800781 1.179688 -2.984375 0.921875 -3.234375 C 0.660156 -3.484375 0.53125 -3.832031 0.53125 -4.28125 C 0.53125 -4.789062 0.710938 -5.191406 1.078125 -5.484375 C 1.441406 -5.785156 1.941406 -5.9375 2.578125 -5.9375 C 2.847656 -5.9375 3.125 -5.910156 3.40625 -5.859375 C 3.695312 -5.816406 3.988281 -5.742188 4.28125 -5.640625 Z M 4.28125 -5.640625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-15">
<path style="stroke:none;" d="M 4.15625 -3.53125 C 4.34375 -3.851562 4.5625 -4.09375 4.8125 -4.25 C 5.0625 -4.40625 5.351562 -4.484375 5.6875 -4.484375 C 6.144531 -4.484375 6.492188 -4.320312 6.734375 -4 C 6.984375 -3.6875 7.109375 -3.234375 7.109375 -2.640625 L 7.109375 0 L 6.390625 0 L 6.390625 -2.625 C 6.390625 -3.039062 6.316406 -3.347656 6.171875 -3.546875 C 6.023438 -3.753906 5.796875 -3.859375 5.484375 -3.859375 C 5.117188 -3.859375 4.828125 -3.734375 4.609375 -3.484375 C 4.390625 -3.234375 4.28125 -2.894531 4.28125 -2.46875 L 4.28125 0 L 3.5625 0 L 3.5625 -2.625 C 3.5625 -3.039062 3.484375 -3.347656 3.328125 -3.546875 C 3.179688 -3.753906 2.957031 -3.859375 2.65625 -3.859375 C 2.28125 -3.859375 1.984375 -3.734375 1.765625 -3.484375 C 1.554688 -3.234375 1.453125 -2.894531 1.453125 -2.46875 L 1.453125 0 L 0.734375 0 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.703125 C 1.617188 -3.960938 1.816406 -4.15625 2.046875 -4.28125 C 2.273438 -4.414062 2.546875 -4.484375 2.859375 -4.484375 C 3.171875 -4.484375 3.4375 -4.398438 3.65625 -4.234375 C 3.882812 -4.078125 4.050781 -3.84375 4.15625 -3.53125 Z M 4.15625 -3.53125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-16">
<path style="stroke:none;" d="M 1.46875 -5.625 L 1.46875 -4.375 L 2.953125 -4.375 L 2.953125 -3.8125 L 1.46875 -3.8125 L 1.46875 -1.4375 C 1.46875 -1.082031 1.515625 -0.851562 1.609375 -0.75 C 1.710938 -0.65625 1.910156 -0.609375 2.203125 -0.609375 L 2.953125 -0.609375 L 2.953125 0 L 2.203125 0 C 1.648438 0 1.269531 -0.101562 1.0625 -0.3125 C 0.851562 -0.519531 0.75 -0.894531 0.75 -1.4375 L 0.75 -3.8125 L 0.21875 -3.8125 L 0.21875 -4.375 L 0.75 -4.375 L 0.75 -5.625 Z M 1.46875 -5.625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-17">
<path style="stroke:none;" d="M 3.640625 -3.71875 L 3.640625 -6.078125 L 4.359375 -6.078125 L 4.359375 0 L 3.640625 0 L 3.640625 -0.65625 C 3.484375 -0.394531 3.289062 -0.203125 3.0625 -0.078125 C 2.832031 0.046875 2.554688 0.109375 2.234375 0.109375 C 1.703125 0.109375 1.269531 -0.0976562 0.9375 -0.515625 C 0.601562 -0.941406 0.4375 -1.5 0.4375 -2.1875 C 0.4375 -2.875 0.601562 -3.425781 0.9375 -3.84375 C 1.269531 -4.269531 1.703125 -4.484375 2.234375 -4.484375 C 2.554688 -4.484375 2.832031 -4.421875 3.0625 -4.296875 C 3.289062 -4.171875 3.484375 -3.976562 3.640625 -3.71875 Z M 1.1875 -2.1875 C 1.1875 -1.65625 1.296875 -1.238281 1.515625 -0.9375 C 1.734375 -0.632812 2.03125 -0.484375 2.40625 -0.484375 C 2.789062 -0.484375 3.09375 -0.632812 3.3125 -0.9375 C 3.53125 -1.238281 3.640625 -1.65625 3.640625 -2.1875 C 3.640625 -2.71875 3.53125 -3.128906 3.3125 -3.421875 C 3.09375 -3.722656 2.789062 -3.875 2.40625 -3.875 C 2.03125 -3.875 1.734375 -3.722656 1.515625 -3.421875 C 1.296875 -3.128906 1.1875 -2.71875 1.1875 -2.1875 Z M 1.1875 -2.1875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-18">
<path style="stroke:none;" d="M 2.453125 -3.875 C 2.066406 -3.875 1.757812 -3.722656 1.53125 -3.421875 C 1.3125 -3.117188 1.203125 -2.707031 1.203125 -2.1875 C 1.203125 -1.664062 1.3125 -1.253906 1.53125 -0.953125 C 1.757812 -0.648438 2.066406 -0.5 2.453125 -0.5 C 2.835938 -0.5 3.140625 -0.648438 3.359375 -0.953125 C 3.585938 -1.253906 3.703125 -1.664062 3.703125 -2.1875 C 3.703125 -2.707031 3.585938 -3.117188 3.359375 -3.421875 C 3.140625 -3.722656 2.835938 -3.875 2.453125 -3.875 Z M 2.453125 -4.484375 C 3.078125 -4.484375 3.566406 -4.28125 3.921875 -3.875 C 4.273438 -3.46875 4.453125 -2.90625 4.453125 -2.1875 C 4.453125 -1.46875 4.273438 -0.90625 3.921875 -0.5 C 3.566406 -0.09375 3.078125 0.109375 2.453125 0.109375 C 1.828125 0.109375 1.332031 -0.09375 0.96875 -0.5 C 0.613281 -0.90625 0.4375 -1.46875 0.4375 -2.1875 C 0.4375 -2.90625 0.613281 -3.46875 0.96875 -3.875 C 1.332031 -4.28125 1.828125 -4.484375 2.453125 -4.484375 Z M 2.453125 -4.484375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-19">
<path style="stroke:none;" d="M 0.640625 -6.078125 L 1.265625 -6.078125 C 1.660156 -5.460938 1.953125 -4.859375 2.140625 -4.265625 C 2.335938 -3.679688 2.4375 -3.097656 2.4375 -2.515625 C 2.4375 -1.929688 2.335938 -1.34375 2.140625 -0.75 C 1.953125 -0.15625 1.660156 0.445312 1.265625 1.0625 L 0.640625 1.0625 C 0.984375 0.457031 1.238281 -0.132812 1.40625 -0.71875 C 1.582031 -1.3125 1.671875 -1.910156 1.671875 -2.515625 C 1.671875 -3.117188 1.582031 -3.710938 1.40625 -4.296875 C 1.238281 -4.878906 0.984375 -5.472656 0.640625 -6.078125 Z M 0.640625 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-20">
<path style="stroke:none;" d="M 3.890625 -2.1875 C 3.890625 -2.71875 3.78125 -3.128906 3.5625 -3.421875 C 3.351562 -3.722656 3.054688 -3.875 2.671875 -3.875 C 2.296875 -3.875 2 -3.722656 1.78125 -3.421875 C 1.5625 -3.128906 1.453125 -2.71875 1.453125 -2.1875 C 1.453125 -1.65625 1.5625 -1.238281 1.78125 -0.9375 C 2 -0.632812 2.296875 -0.484375 2.671875 -0.484375 C 3.054688 -0.484375 3.351562 -0.632812 3.5625 -0.9375 C 3.78125 -1.238281 3.890625 -1.65625 3.890625 -2.1875 Z M 1.453125 -3.71875 C 1.597656 -3.976562 1.785156 -4.171875 2.015625 -4.296875 C 2.253906 -4.421875 2.53125 -4.484375 2.84375 -4.484375 C 3.375 -4.484375 3.804688 -4.269531 4.140625 -3.84375 C 4.472656 -3.425781 4.640625 -2.875 4.640625 -2.1875 C 4.640625 -1.5 4.472656 -0.941406 4.140625 -0.515625 C 3.804688 -0.0976562 3.375 0.109375 2.84375 0.109375 C 2.53125 0.109375 2.253906 0.046875 2.015625 -0.078125 C 1.785156 -0.203125 1.597656 -0.394531 1.453125 -0.65625 L 1.453125 0 L 0.734375 0 L 0.734375 -6.078125 L 1.453125 -6.078125 Z M 1.453125 -3.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-21">
<path style="stroke:none;" d="M 4.390625 -2.640625 L 4.390625 0 L 3.671875 0 L 3.671875 -2.625 C 3.671875 -3.03125 3.585938 -3.335938 3.421875 -3.546875 C 3.265625 -3.753906 3.023438 -3.859375 2.703125 -3.859375 C 2.316406 -3.859375 2.007812 -3.734375 1.78125 -3.484375 C 1.5625 -3.234375 1.453125 -2.894531 1.453125 -2.46875 L 1.453125 0 L 0.734375 0 L 0.734375 -6.078125 L 1.453125 -6.078125 L 1.453125 -3.703125 C 1.617188 -3.960938 1.816406 -4.15625 2.046875 -4.28125 C 2.285156 -4.414062 2.554688 -4.484375 2.859375 -4.484375 C 3.367188 -4.484375 3.75 -4.328125 4 -4.015625 C 4.257812 -3.703125 4.390625 -3.242188 4.390625 -2.640625 Z M 4.390625 -2.640625 "/>
</symbol>
</g>
<clipPath id="clip1">
<path d="M 1053.125 6 L 1121.109375 6 L 1121.109375 19 L 1053.125 19 Z M 1053.125 6 "/>
</clipPath>
<clipPath id="clip2">
<path d="M 1149 6 L 1155.90625 6 L 1155.90625 19 L 1149 19 Z M 1149 6 "/>
</clipPath>
<clipPath id="clip3">
<path d="M 506.714844 70 L 528 70 L 528 76 L 506.714844 76 Z M 506.714844 70 "/>
</clipPath>
<clipPath id="clip4">
<path d="M 84.230469 37 L 102 37 L 102 45 L 84.230469 45 Z M 84.230469 37 "/>
</clipPath>
</defs>
<g id="surface2">
<rect x="0" y="0" width="1280" height="120" style="fill:rgb(90%,90%,90%);fill-opacity:1;stroke:none;"/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(90%,90%,45%);fill-opacity:1;" d="M 1051.125 4 L 1119.308594 4 L 1119.308594 100 L 1051.125 100 Z M 1051.125 4 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(90%,90%,45%);fill-opacity:1;" d="M 1146.546875 4 L 1154.105469 4 L 1154.105469 100 L 1146.546875 100 Z M 1146.546875 4 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 4 0 L 4 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 234.425781 0 L 234.425781 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 464.851562 0 L 464.851562 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 695.28125 0 L 695.28125 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 925.707031 0 L 925.707031 120 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1156.132812 0 L 1156.132812 120 "/>
<g style="fill:rgb(40%,40%,40%);fill-opacity:1;">
<use xlink:href="#glyph0-1" x="4" y="115.503906"/>
<use xlink:href="#glyph0-2" x="11.582031" y="115.503906"/>
<use xlink:href="#glyph0-3" x="18.935547" y="115.503906"/>
<use xlink:href="#glyph0-4" x="25.533203" y="115.503906"/>
<use xlink:href="#glyph0-5" x="33.138672" y="115.503906"/>
<use xlink:href="#glyph0-6" x="36.953125" y="115.503906"/>
<use xlink:href="#glyph0-7" x="44.570312" y="115.503906"/>
<use xlink:href="#glyph0-8" x="49.503906" y="115.503906"/>
<use xlink:href="#glyph0-9" x="52.837891" y="115.503906"/>
<use xlink:href="#glyph0-5" x="60.455078" y="115.503906"/>
<use xlink:href="#glyph0-9" x="64.269531" y="115.503906"/>
<use xlink:href="#glyph0-8" x="71.886719" y="115.503906"/>
<use xlink:href="#glyph0-10" x="75.220703" y="115.503906"/>
<use xlink:href="#glyph0-8" x="82.322266" y="115.503906"/>
<use xlink:href="#glyph0-11" x="85.65625" y="115.503906"/>
<use xlink:href="#glyph0-8" x="91.908203" y="115.503906"/>
<use xlink:href="#glyph0-12" x="95.242188" y="115.503906"/>
<use xlink:href="#glyph0-13" x="102.583984" y="115.503906"/>
<use xlink:href="#glyph0-14" x="110.189453" y="115.503906"/>
<use xlink:href="#glyph0-5" x="114.232422" y="115.503906"/>
<use xlink:href="#glyph0-15" x="118.046875" y="115.503906"/>
<use xlink:href="#glyph0-16" x="125.681641" y="115.503906"/>
<use xlink:href="#glyph0-5" x="133.316406" y="115.503906"/>
<use xlink:href="#glyph0-17" x="137.130859" y="115.503906"/>
<use xlink:href="#glyph0-11" x="144.736328" y="115.503906"/>
</g>
<g clip-path="url(#clip1)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="1053.125" y="16"/>
<use xlink:href="#glyph0-17" x="1059.376953" y="16"/>
<use xlink:href="#glyph0-11" x="1066.982422" y="16"/>
<use xlink:href="#glyph0-18" x="1073.234375" y="16"/>
<use xlink:href="#glyph0-19" x="1080.851562" y="16"/>
<use xlink:href="#glyph0-13" x="1088.234375" y="16"/>
<use xlink:href="#glyph0-9" x="1095.839844" y="16"/>
<use xlink:href="#glyph0-20" x="1103.457031" y="16"/>
<use xlink:href="#glyph0-9" x="1107.787109" y="16"/>
<use xlink:href="#glyph0-12" x="1115.404297" y="16"/>
<use xlink:href="#glyph0-21" x="1122.746094" y="16"/>
<use xlink:href="#glyph0-2" x="1134.435547" y="16"/>
<use xlink:href="#glyph0-8" x="1141.789062" y="16"/>
<use xlink:href="#glyph0-13" x="1145.123047" y="16"/>
</g>
</g>
<g clip-path="url(#clip2)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="1148.546875" y="16"/>
<use xlink:href="#glyph0-17" x="1154.798828" y="16"/>
<use xlink:href="#glyph0-11" x="1162.404297" y="16"/>
<use xlink:href="#glyph0-18" x="1168.65625" y="16"/>
<use xlink:href="#glyph0-19" x="1176.273438" y="16"/>
<use xlink:href="#glyph0-13" x="1183.65625" y="16"/>
<use xlink:href="#glyph0-9" x="1191.261719" y="16"/>
<use xlink:href="#glyph0-20" x="1198.878906" y="16"/>
<use xlink:href="#glyph0-9" x="1203.208984" y="16"/>
<use xlink:href="#glyph0-12" x="1210.826172" y="16"/>
<use xlink:href="#glyph0-21" x="1218.167969" y="16"/>
<use xlink:href="#glyph0-2" x="1229.857422" y="16"/>
<use xlink:href="#glyph0-8" x="1237.210938" y="16"/>
<use xlink:href="#glyph0-13" x="1240.544922" y="16"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 12.019531 46 L 22.988281 46 L 22.988281 60 L 12.019531 60 Z M 12.019531 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 22.988281 46 L 147.027344 46 L 147.027344 60 L 22.988281 60 Z M 22.988281 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 147.027344 46 L 791.160156 46 L 791.160156 60 L 147.027344 60 Z M 147.027344 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 791.160156 46 L 1007.484375 46 L 1007.484375 60 L 791.160156 60 Z M 791.160156 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1007.484375 46 L 1155.441406 46 L 1155.441406 60 L 1007.484375 60 Z M 1007.484375 46 "/>
<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
<use xlink:href="#glyph0-22" x="1009.484375" y="58"/>
<use xlink:href="#glyph0-23" x="1017.101562" y="58"/>
<use xlink:href="#glyph0-8" x="1026.916016" y="58"/>
<use xlink:href="#glyph0-24" x="1030.25" y="58"/>
<use xlink:href="#glyph0-3" x="1034.955078" y="58"/>
<use xlink:href="#glyph0-4" x="1041.552734" y="58"/>
<use xlink:href="#glyph0-25" x="1049.158203" y="58"/>
<use xlink:href="#glyph0-2" x="1052.972656" y="58"/>
<use xlink:href="#glyph0-23" x="1060.326172" y="58"/>
<use xlink:href="#glyph0-2" x="1070.140625" y="58"/>
<use xlink:href="#glyph0-8" x="1077.494141" y="58"/>
<use xlink:href="#glyph0-24" x="1080.828125" y="58"/>
<use xlink:href="#glyph0-26" x="1085.533203" y="58"/>
<use xlink:href="#glyph0-8" x="1091.533203" y="58"/>
<use xlink:href="#glyph0-9" x="1094.867188" y="58"/>
<use xlink:href="#glyph0-27" x="1102.484375" y="58"/>
<use xlink:href="#glyph0-19" x="1105.818359" y="58"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1155.441406 46 L 1224.984375 46 L 1224.984375 60 L 1155.441406 60 Z M 1155.441406 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1224.984375 46 L 1276 46 L 1276 60 L 1224.984375 60 Z M 1224.984375 46 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-3" x="1226.984375" y="58"/>
<use xlink:href="#glyph0-27" x="1233.582031" y="58"/>
<use xlink:href="#glyph0-12" x="1236.916016" y="58"/>
<use xlink:href="#glyph0-11" x="1244.257812" y="58"/>
<use xlink:href="#glyph0-19" x="1250.509766" y="58"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1276 46 L 1177.722656 46 L 1177.722656 60 L 1276 60 Z M 1276 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 147.027344 78 L 152.121094 78 L 152.121094 92 L 147.027344 92 Z M 147.027344 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 152.117188 78 L 335.929688 78 L 335.929688 92 L 152.117188 92 Z M 152.117188 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 335.929688 78 L 763.671875 78 L 763.671875 92 L 335.929688 92 Z M 335.929688 78 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-24" x="337.929688" y="90"/>
<use xlink:href="#glyph0-7" x="342.634766" y="90"/>
<use xlink:href="#glyph0-2" x="347.568359" y="90"/>
<use xlink:href="#glyph0-3" x="354.921875" y="90"/>
<use xlink:href="#glyph0-19" x="361.519531" y="90"/>
<use xlink:href="#glyph0-27" x="368.902344" y="90"/>
<use xlink:href="#glyph0-13" x="372.236328" y="90"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 763.667969 78 L 771.59375 78 L 771.59375 92 L 763.667969 92 Z M 763.667969 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 771.597656 78 L 1122.46875 78 L 1122.46875 92 L 771.597656 92 Z M 771.597656 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1122.46875 78 L 1155.441406 78 L 1155.441406 92 L 1122.46875 92 Z M 1122.46875 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1155.441406 78 L 1142.445312 78 L 1142.445312 92 L 1155.441406 92 Z M 1155.441406 78 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 504.714844 81 L 504.714844 75 "/>
<g clip-path="url(#clip3)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="506.714844" y="76"/>
<use xlink:href="#glyph1-2" x="511.449219" y="76"/>
<use xlink:href="#glyph1-3" x="513.992188" y="76"/>
<use xlink:href="#glyph1-2" x="520.695312" y="76"/>
<use xlink:href="#glyph1-4" x="523.238281" y="76"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 147.027344 46 L 147.027344 92 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(80%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 992.34375 36 L 992.34375 100 "/>
<g style="fill:rgb(80%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-5" x="994.34375" y="44"/>
<use xlink:href="#glyph1-6" x="998.945312" y="44"/>
<use xlink:href="#glyph1-7" x="1003.847656" y="44"/>
<use xlink:href="#glyph1-8" x="1006.070312" y="44"/>
<use xlink:href="#glyph1-9" x="1008.292969" y="44"/>
<use xlink:href="#glyph1-10" x="1013.363281" y="44"/>
<use xlink:href="#glyph1-11" x="1016.652344" y="44"/>
<use xlink:href="#glyph1-12" x="1021.574219" y="44"/>
<use xlink:href="#glyph1-13" x="1024.695312" y="44"/>
<use xlink:href="#glyph1-14" x="1028.375" y="44"/>
<use xlink:href="#glyph1-7" x="1033.453125" y="44"/>
<use xlink:href="#glyph1-15" x="1035.675781" y="44"/>
<use xlink:href="#glyph1-9" x="1043.46875" y="44"/>
<use xlink:href="#glyph1-8" x="1048.539062" y="44"/>
<use xlink:href="#glyph1-6" x="1050.761719" y="44"/>
<use xlink:href="#glyph1-16" x="1055.664062" y="44"/>
<use xlink:href="#glyph1-11" x="1058.800781" y="44"/>
<use xlink:href="#glyph1-17" x="1063.722656" y="44"/>
<use xlink:href="#glyph1-2" x="1068.800781" y="44"/>
<use xlink:href="#glyph1-11" x="1071.34375" y="44"/>
<use xlink:href="#glyph1-10" x="1076.265625" y="44"/>
<use xlink:href="#glyph1-10" x="1079.554688" y="44"/>
<use xlink:href="#glyph1-18" x="1082.84375" y="44"/>
<use xlink:href="#glyph1-10" x="1087.738281" y="44"/>
<use xlink:href="#glyph1-13" x="1091.027344" y="44"/>
<use xlink:href="#glyph1-19" x="1094.707031" y="44"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 86.230469 36 L 82.230469 36 L 82.230469 96 L 86.230469 96 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1170.613281 36 L 1174.613281 36 L 1174.613281 96 L 1170.613281 96 "/>
<g clip-path="url(#clip4)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-20" x="84.230469" y="44"/>
<use xlink:href="#glyph1-18" x="89.308594" y="44"/>
<use xlink:href="#glyph1-16" x="94.203125" y="44"/>
<use xlink:href="#glyph1-21" x="97.339844" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 12.019531 46 L 12.019531 60 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 138 KiB

BIN
doc/traces/net-posix.fxt Normal file

Binary file not shown.

832
doc/traces/net-posix.svg Normal file
View File

@ -0,0 +1,832 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1280pt" height="152pt" viewBox="0 0 1280 152" version="1.1">
<defs>
<g>
<symbol overflow="visible" id="glyph0-0">
<path style="stroke:none;" d="M 0.59375 2.125 L 0.59375 -8.46875 L 6.59375 -8.46875 L 6.59375 2.125 Z M 1.265625 1.453125 L 5.9375 1.453125 L 5.9375 -7.78125 L 1.265625 -7.78125 Z M 1.265625 1.453125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-1">
<path style="stroke:none;" d="M 1.171875 -8.75 L 6.703125 -8.75 L 6.703125 -7.75 L 2.359375 -7.75 L 2.359375 -5.15625 L 6.53125 -5.15625 L 6.53125 -4.171875 L 2.359375 -4.171875 L 2.359375 -1 L 6.8125 -1 L 6.8125 0 L 1.171875 0 Z M 1.171875 -8.75 "/>
</symbol>
<symbol overflow="visible" id="glyph0-2">
<path style="stroke:none;" d="M 4.109375 -3.296875 C 3.242188 -3.296875 2.640625 -3.195312 2.296875 -3 C 1.960938 -2.800781 1.796875 -2.460938 1.796875 -1.984375 C 1.796875 -1.597656 1.921875 -1.289062 2.171875 -1.0625 C 2.429688 -0.84375 2.773438 -0.734375 3.203125 -0.734375 C 3.804688 -0.734375 4.285156 -0.941406 4.640625 -1.359375 C 5.003906 -1.785156 5.1875 -2.351562 5.1875 -3.0625 L 5.1875 -3.296875 Z M 6.265625 -3.75 L 6.265625 0 L 5.1875 0 L 5.1875 -1 C 4.9375 -0.601562 4.628906 -0.304688 4.265625 -0.109375 C 3.898438 0.078125 3.453125 0.171875 2.921875 0.171875 C 2.242188 0.171875 1.707031 -0.015625 1.3125 -0.390625 C 0.914062 -0.773438 0.71875 -1.28125 0.71875 -1.90625 C 0.71875 -2.644531 0.960938 -3.203125 1.453125 -3.578125 C 1.953125 -3.953125 2.691406 -4.140625 3.671875 -4.140625 L 5.1875 -4.140625 L 5.1875 -4.25 C 5.1875 -4.75 5.019531 -5.132812 4.6875 -5.40625 C 4.363281 -5.675781 3.910156 -5.8125 3.328125 -5.8125 C 2.953125 -5.8125 2.582031 -5.765625 2.21875 -5.671875 C 1.863281 -5.578125 1.523438 -5.441406 1.203125 -5.265625 L 1.203125 -6.265625 C 1.597656 -6.421875 1.976562 -6.535156 2.34375 -6.609375 C 2.71875 -6.679688 3.082031 -6.71875 3.4375 -6.71875 C 4.382812 -6.71875 5.09375 -6.472656 5.5625 -5.984375 C 6.03125 -5.492188 6.265625 -4.75 6.265625 -3.75 Z M 6.265625 -3.75 "/>
</symbol>
<symbol overflow="visible" id="glyph0-3">
<path style="stroke:none;" d="M 5.859375 -6.3125 L 5.859375 -5.296875 C 5.546875 -5.472656 5.238281 -5.601562 4.9375 -5.6875 C 4.632812 -5.769531 4.328125 -5.8125 4.015625 -5.8125 C 3.304688 -5.8125 2.757812 -5.585938 2.375 -5.140625 C 1.988281 -4.703125 1.796875 -4.082031 1.796875 -3.28125 C 1.796875 -2.476562 1.988281 -1.851562 2.375 -1.40625 C 2.757812 -0.96875 3.304688 -0.75 4.015625 -0.75 C 4.328125 -0.75 4.632812 -0.789062 4.9375 -0.875 C 5.238281 -0.957031 5.546875 -1.082031 5.859375 -1.25 L 5.859375 -0.25 C 5.554688 -0.113281 5.242188 -0.0078125 4.921875 0.0625 C 4.597656 0.132812 4.253906 0.171875 3.890625 0.171875 C 2.898438 0.171875 2.113281 -0.132812 1.53125 -0.75 C 0.945312 -1.375 0.65625 -2.21875 0.65625 -3.28125 C 0.65625 -4.34375 0.945312 -5.179688 1.53125 -5.796875 C 2.125 -6.410156 2.9375 -6.71875 3.96875 -6.71875 C 4.289062 -6.71875 4.609375 -6.679688 4.921875 -6.609375 C 5.242188 -6.546875 5.554688 -6.445312 5.859375 -6.3125 Z M 5.859375 -6.3125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-4">
<path style="stroke:none;" d="M 6.59375 -3.96875 L 6.59375 0 L 5.515625 0 L 5.515625 -3.921875 C 5.515625 -4.546875 5.390625 -5.007812 5.140625 -5.3125 C 4.898438 -5.625 4.539062 -5.78125 4.0625 -5.78125 C 3.476562 -5.78125 3.015625 -5.59375 2.671875 -5.21875 C 2.335938 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -9.125 L 2.171875 -9.125 L 2.171875 -5.546875 C 2.429688 -5.941406 2.734375 -6.234375 3.078125 -6.421875 C 3.429688 -6.617188 3.835938 -6.71875 4.296875 -6.71875 C 5.046875 -6.71875 5.613281 -6.484375 6 -6.015625 C 6.394531 -5.554688 6.59375 -4.875 6.59375 -3.96875 Z M 6.59375 -3.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-5">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph0-6">
<path style="stroke:none;" d="M 5.453125 -3.359375 C 5.453125 -4.140625 5.289062 -4.742188 4.96875 -5.171875 C 4.644531 -5.597656 4.191406 -5.8125 3.609375 -5.8125 C 3.035156 -5.8125 2.585938 -5.597656 2.265625 -5.171875 C 1.941406 -4.742188 1.78125 -4.140625 1.78125 -3.359375 C 1.78125 -2.578125 1.941406 -1.972656 2.265625 -1.546875 C 2.585938 -1.117188 3.035156 -0.90625 3.609375 -0.90625 C 4.191406 -0.90625 4.644531 -1.117188 4.96875 -1.546875 C 5.289062 -1.972656 5.453125 -2.578125 5.453125 -3.359375 Z M 6.53125 -0.8125 C 6.53125 0.300781 6.28125 1.128906 5.78125 1.671875 C 5.289062 2.222656 4.53125 2.5 3.5 2.5 C 3.125 2.5 2.765625 2.46875 2.421875 2.40625 C 2.085938 2.351562 1.765625 2.269531 1.453125 2.15625 L 1.453125 1.109375 C 1.765625 1.273438 2.078125 1.398438 2.390625 1.484375 C 2.703125 1.566406 3.015625 1.609375 3.328125 1.609375 C 4.035156 1.609375 4.566406 1.421875 4.921875 1.046875 C 5.273438 0.679688 5.453125 0.125 5.453125 -0.625 L 5.453125 -1.15625 C 5.222656 -0.769531 4.9375 -0.476562 4.59375 -0.28125 C 4.25 -0.09375 3.832031 0 3.34375 0 C 2.539062 0 1.890625 -0.304688 1.390625 -0.921875 C 0.898438 -1.535156 0.65625 -2.347656 0.65625 -3.359375 C 0.65625 -4.367188 0.898438 -5.179688 1.390625 -5.796875 C 1.890625 -6.410156 2.539062 -6.71875 3.34375 -6.71875 C 3.832031 -6.71875 4.25 -6.617188 4.59375 -6.421875 C 4.9375 -6.234375 5.222656 -5.945312 5.453125 -5.5625 L 5.453125 -6.5625 L 6.53125 -6.5625 Z M 6.53125 -0.8125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-7">
<path style="stroke:none;" d="M 4.9375 -5.5625 C 4.8125 -5.625 4.675781 -5.671875 4.53125 -5.703125 C 4.394531 -5.742188 4.238281 -5.765625 4.0625 -5.765625 C 3.457031 -5.765625 2.988281 -5.566406 2.65625 -5.171875 C 2.332031 -4.773438 2.171875 -4.203125 2.171875 -3.453125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.398438 -5.941406 2.695312 -6.234375 3.0625 -6.421875 C 3.425781 -6.617188 3.867188 -6.71875 4.390625 -6.71875 C 4.460938 -6.71875 4.539062 -6.710938 4.625 -6.703125 C 4.71875 -6.691406 4.816406 -6.675781 4.921875 -6.65625 Z M 4.9375 -5.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-8">
<path style="stroke:none;" d="M 1.125 -6.5625 L 2.203125 -6.5625 L 2.203125 0 L 1.125 0 Z M 1.125 -9.125 L 2.203125 -9.125 L 2.203125 -7.75 L 1.125 -7.75 Z M 1.125 -9.125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-9">
<path style="stroke:none;" d="M 5.453125 -5.5625 L 5.453125 -9.125 L 6.53125 -9.125 L 6.53125 0 L 5.453125 0 L 5.453125 -0.984375 C 5.222656 -0.597656 4.9375 -0.304688 4.59375 -0.109375 C 4.25 0.078125 3.832031 0.171875 3.34375 0.171875 C 2.550781 0.171875 1.90625 -0.144531 1.40625 -0.78125 C 0.90625 -1.414062 0.65625 -2.25 0.65625 -3.28125 C 0.65625 -4.3125 0.90625 -5.140625 1.40625 -5.765625 C 1.90625 -6.398438 2.550781 -6.71875 3.34375 -6.71875 C 3.832031 -6.71875 4.25 -6.625 4.59375 -6.4375 C 4.9375 -6.25 5.222656 -5.957031 5.453125 -5.5625 Z M 1.78125 -3.28125 C 1.78125 -2.488281 1.941406 -1.863281 2.265625 -1.40625 C 2.585938 -0.957031 3.035156 -0.734375 3.609375 -0.734375 C 4.179688 -0.734375 4.628906 -0.957031 4.953125 -1.40625 C 5.285156 -1.863281 5.453125 -2.488281 5.453125 -3.28125 C 5.453125 -4.070312 5.285156 -4.691406 4.953125 -5.140625 C 4.628906 -5.585938 4.179688 -5.8125 3.609375 -5.8125 C 3.035156 -5.8125 2.585938 -5.585938 2.265625 -5.140625 C 1.941406 -4.691406 1.78125 -4.070312 1.78125 -3.28125 Z M 1.78125 -3.28125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-10">
<path style="stroke:none;" d="M 0.359375 -6.5625 L 1.5 -6.5625 L 3.546875 -1.0625 L 5.609375 -6.5625 L 6.75 -6.5625 L 4.28125 0 L 2.8125 0 Z M 0.359375 -6.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-11">
<path style="stroke:none;" d="M 5.3125 -6.375 L 5.3125 -5.34375 C 5.007812 -5.5 4.691406 -5.613281 4.359375 -5.6875 C 4.035156 -5.769531 3.695312 -5.8125 3.34375 -5.8125 C 2.8125 -5.8125 2.410156 -5.726562 2.140625 -5.5625 C 1.867188 -5.40625 1.734375 -5.160156 1.734375 -4.828125 C 1.734375 -4.578125 1.828125 -4.378906 2.015625 -4.234375 C 2.210938 -4.097656 2.601562 -3.96875 3.1875 -3.84375 L 3.546875 -3.75 C 4.316406 -3.59375 4.863281 -3.363281 5.1875 -3.0625 C 5.507812 -2.757812 5.671875 -2.34375 5.671875 -1.8125 C 5.671875 -1.195312 5.425781 -0.710938 4.9375 -0.359375 C 4.457031 -0.00390625 3.796875 0.171875 2.953125 0.171875 C 2.597656 0.171875 2.226562 0.132812 1.84375 0.0625 C 1.46875 0 1.070312 -0.0976562 0.65625 -0.234375 L 0.65625 -1.359375 C 1.050781 -1.148438 1.441406 -0.992188 1.828125 -0.890625 C 2.210938 -0.785156 2.597656 -0.734375 2.984375 -0.734375 C 3.484375 -0.734375 3.867188 -0.816406 4.140625 -0.984375 C 4.421875 -1.160156 4.5625 -1.410156 4.5625 -1.734375 C 4.5625 -2.023438 4.460938 -2.25 4.265625 -2.40625 C 4.066406 -2.5625 3.632812 -2.710938 2.96875 -2.859375 L 2.59375 -2.9375 C 1.925781 -3.082031 1.441406 -3.300781 1.140625 -3.59375 C 0.847656 -3.882812 0.703125 -4.28125 0.703125 -4.78125 C 0.703125 -5.40625 0.921875 -5.882812 1.359375 -6.21875 C 1.796875 -6.550781 2.414062 -6.71875 3.21875 -6.71875 C 3.613281 -6.71875 3.988281 -6.6875 4.34375 -6.625 C 4.695312 -6.570312 5.019531 -6.488281 5.3125 -6.375 Z M 5.3125 -6.375 "/>
</symbol>
<symbol overflow="visible" id="glyph0-12">
<path style="stroke:none;" d="M 3.671875 -5.8125 C 3.097656 -5.8125 2.640625 -5.582031 2.296875 -5.125 C 1.960938 -4.675781 1.796875 -4.0625 1.796875 -3.28125 C 1.796875 -2.488281 1.960938 -1.867188 2.296875 -1.421875 C 2.628906 -0.972656 3.085938 -0.75 3.671875 -0.75 C 4.242188 -0.75 4.695312 -0.972656 5.03125 -1.421875 C 5.375 -1.878906 5.546875 -2.5 5.546875 -3.28125 C 5.546875 -4.050781 5.375 -4.664062 5.03125 -5.125 C 4.695312 -5.582031 4.242188 -5.8125 3.671875 -5.8125 Z M 3.671875 -6.71875 C 4.609375 -6.71875 5.34375 -6.410156 5.875 -5.796875 C 6.414062 -5.191406 6.6875 -4.351562 6.6875 -3.28125 C 6.6875 -2.207031 6.414062 -1.363281 5.875 -0.75 C 5.34375 -0.132812 4.609375 0.171875 3.671875 0.171875 C 2.734375 0.171875 1.992188 -0.132812 1.453125 -0.75 C 0.921875 -1.363281 0.65625 -2.207031 0.65625 -3.28125 C 0.65625 -4.351562 0.921875 -5.191406 1.453125 -5.796875 C 1.992188 -6.410156 2.734375 -6.71875 3.671875 -6.71875 Z M 3.671875 -6.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-13">
<path style="stroke:none;" d="M 6.59375 -3.96875 L 6.59375 0 L 5.515625 0 L 5.515625 -3.921875 C 5.515625 -4.546875 5.390625 -5.007812 5.140625 -5.3125 C 4.898438 -5.625 4.539062 -5.78125 4.0625 -5.78125 C 3.476562 -5.78125 3.015625 -5.59375 2.671875 -5.21875 C 2.335938 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.429688 -5.941406 2.734375 -6.234375 3.078125 -6.421875 C 3.429688 -6.617188 3.835938 -6.71875 4.296875 -6.71875 C 5.046875 -6.71875 5.613281 -6.484375 6 -6.015625 C 6.394531 -5.554688 6.59375 -4.875 6.59375 -3.96875 Z M 6.59375 -3.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-14">
<path style="stroke:none;" d="M 1.40625 -1.484375 L 2.640625 -1.484375 L 2.640625 0 L 1.40625 0 Z M 1.40625 -6.203125 L 2.640625 -6.203125 L 2.640625 -4.71875 L 1.40625 -4.71875 Z M 1.40625 -6.203125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-15">
<path style="stroke:none;" d="M 1.484375 -1 L 3.421875 -1 L 3.421875 -7.671875 L 1.3125 -7.25 L 1.3125 -8.328125 L 3.40625 -8.75 L 4.59375 -8.75 L 4.59375 -1 L 6.53125 -1 L 6.53125 0 L 1.484375 0 Z M 1.484375 -1 "/>
</symbol>
<symbol overflow="visible" id="glyph0-16">
<path style="stroke:none;" d="M 3.8125 -7.96875 C 3.207031 -7.96875 2.75 -7.664062 2.4375 -7.0625 C 2.132812 -6.46875 1.984375 -5.566406 1.984375 -4.359375 C 1.984375 -3.160156 2.132812 -2.257812 2.4375 -1.65625 C 2.75 -1.0625 3.207031 -0.765625 3.8125 -0.765625 C 4.425781 -0.765625 4.882812 -1.0625 5.1875 -1.65625 C 5.5 -2.257812 5.65625 -3.160156 5.65625 -4.359375 C 5.65625 -5.566406 5.5 -6.46875 5.1875 -7.0625 C 4.882812 -7.664062 4.425781 -7.96875 3.8125 -7.96875 Z M 3.8125 -8.90625 C 4.789062 -8.90625 5.539062 -8.515625 6.0625 -7.734375 C 6.582031 -6.960938 6.84375 -5.835938 6.84375 -4.359375 C 6.84375 -2.890625 6.582031 -1.765625 6.0625 -0.984375 C 5.539062 -0.210938 4.789062 0.171875 3.8125 0.171875 C 2.832031 0.171875 2.082031 -0.210938 1.5625 -0.984375 C 1.050781 -1.765625 0.796875 -2.890625 0.796875 -4.359375 C 0.796875 -5.835938 1.050781 -6.960938 1.5625 -7.734375 C 2.082031 -8.515625 2.832031 -8.90625 3.8125 -8.90625 Z M 3.8125 -8.90625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-17">
<path style="stroke:none;" d="M 1.015625 -2.59375 L 1.015625 -6.5625 L 2.09375 -6.5625 L 2.09375 -2.625 C 2.09375 -2.007812 2.210938 -1.546875 2.453125 -1.234375 C 2.703125 -0.921875 3.066406 -0.765625 3.546875 -0.765625 C 4.128906 -0.765625 4.585938 -0.945312 4.921875 -1.3125 C 5.265625 -1.6875 5.4375 -2.195312 5.4375 -2.84375 L 5.4375 -6.5625 L 6.515625 -6.5625 L 6.515625 0 L 5.4375 0 L 5.4375 -1.015625 C 5.175781 -0.609375 4.875 -0.304688 4.53125 -0.109375 C 4.1875 0.078125 3.785156 0.171875 3.328125 0.171875 C 2.566406 0.171875 1.988281 -0.0625 1.59375 -0.53125 C 1.207031 -1 1.015625 -1.6875 1.015625 -2.59375 Z M 3.734375 -6.71875 Z M 3.734375 -6.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-18">
<path style="stroke:none;" d="M 2.171875 -0.984375 L 2.171875 2.5 L 1.09375 2.5 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.5625 C 2.398438 -5.957031 2.6875 -6.25 3.03125 -6.4375 C 3.375 -6.625 3.785156 -6.71875 4.265625 -6.71875 C 5.066406 -6.71875 5.71875 -6.398438 6.21875 -5.765625 C 6.71875 -5.140625 6.96875 -4.3125 6.96875 -3.28125 C 6.96875 -2.25 6.71875 -1.414062 6.21875 -0.78125 C 5.71875 -0.144531 5.066406 0.171875 4.265625 0.171875 C 3.785156 0.171875 3.375 0.078125 3.03125 -0.109375 C 2.6875 -0.304688 2.398438 -0.597656 2.171875 -0.984375 Z M 5.84375 -3.28125 C 5.84375 -4.070312 5.675781 -4.691406 5.34375 -5.140625 C 5.019531 -5.585938 4.578125 -5.8125 4.015625 -5.8125 C 3.441406 -5.8125 2.988281 -5.585938 2.65625 -5.140625 C 2.332031 -4.691406 2.171875 -4.070312 2.171875 -3.28125 C 2.171875 -2.488281 2.332031 -1.863281 2.65625 -1.40625 C 2.988281 -0.957031 3.441406 -0.734375 4.015625 -0.734375 C 4.578125 -0.734375 5.019531 -0.957031 5.34375 -1.40625 C 5.675781 -1.863281 5.84375 -2.488281 5.84375 -3.28125 Z M 5.84375 -3.28125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-19">
<path style="stroke:none;" d="M 6.75 -3.546875 L 6.75 -3.03125 L 1.78125 -3.03125 C 1.832031 -2.28125 2.054688 -1.710938 2.453125 -1.328125 C 2.859375 -0.941406 3.414062 -0.75 4.125 -0.75 C 4.539062 -0.75 4.941406 -0.796875 5.328125 -0.890625 C 5.722656 -0.992188 6.113281 -1.148438 6.5 -1.359375 L 6.5 -0.328125 C 6.101562 -0.171875 5.703125 -0.0507812 5.296875 0.03125 C 4.890625 0.125 4.476562 0.171875 4.0625 0.171875 C 3.019531 0.171875 2.191406 -0.128906 1.578125 -0.734375 C 0.960938 -1.347656 0.65625 -2.175781 0.65625 -3.21875 C 0.65625 -4.289062 0.945312 -5.140625 1.53125 -5.765625 C 2.113281 -6.398438 2.894531 -6.71875 3.875 -6.71875 C 4.757812 -6.71875 5.457031 -6.429688 5.96875 -5.859375 C 6.488281 -5.296875 6.75 -4.523438 6.75 -3.546875 Z M 5.671875 -3.875 C 5.660156 -4.457031 5.492188 -4.925781 5.171875 -5.28125 C 4.847656 -5.632812 4.421875 -5.8125 3.890625 -5.8125 C 3.285156 -5.8125 2.800781 -5.640625 2.4375 -5.296875 C 2.082031 -4.953125 1.878906 -4.472656 1.828125 -3.859375 Z M 5.671875 -3.875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-20">
<path style="stroke:none;" d="M 0.59375 -3.765625 L 3.75 -3.765625 L 3.75 -2.8125 L 0.59375 -2.8125 Z M 0.59375 -3.765625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-21">
<path style="stroke:none;" d="M 6.234375 -5.296875 C 6.503906 -5.785156 6.828125 -6.144531 7.203125 -6.375 C 7.578125 -6.601562 8.019531 -6.71875 8.53125 -6.71875 C 9.21875 -6.71875 9.742188 -6.476562 10.109375 -6 C 10.484375 -5.519531 10.671875 -4.84375 10.671875 -3.96875 L 10.671875 0 L 9.59375 0 L 9.59375 -3.921875 C 9.59375 -4.554688 9.476562 -5.023438 9.25 -5.328125 C 9.03125 -5.628906 8.691406 -5.78125 8.234375 -5.78125 C 7.671875 -5.78125 7.226562 -5.59375 6.90625 -5.21875 C 6.582031 -4.851562 6.421875 -4.347656 6.421875 -3.703125 L 6.421875 0 L 5.34375 0 L 5.34375 -3.921875 C 5.34375 -4.554688 5.226562 -5.023438 5 -5.328125 C 4.78125 -5.628906 4.4375 -5.78125 3.96875 -5.78125 C 3.414062 -5.78125 2.976562 -5.59375 2.65625 -5.21875 C 2.332031 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.421875 -5.941406 2.71875 -6.234375 3.0625 -6.421875 C 3.40625 -6.617188 3.8125 -6.71875 4.28125 -6.71875 C 4.757812 -6.71875 5.164062 -6.597656 5.5 -6.359375 C 5.832031 -6.117188 6.078125 -5.765625 6.234375 -5.296875 Z M 6.234375 -5.296875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-22">
<path style="stroke:none;" d="M 2.203125 -8.421875 L 2.203125 -6.5625 L 4.421875 -6.5625 L 4.421875 -5.71875 L 2.203125 -5.71875 L 2.203125 -2.15625 C 2.203125 -1.625 2.273438 -1.28125 2.421875 -1.125 C 2.566406 -0.976562 2.863281 -0.90625 3.3125 -0.90625 L 4.421875 -0.90625 L 4.421875 0 L 3.3125 0 C 2.476562 0 1.898438 -0.15625 1.578125 -0.46875 C 1.265625 -0.78125 1.109375 -1.34375 1.109375 -2.15625 L 1.109375 -5.71875 L 0.328125 -5.71875 L 0.328125 -6.5625 L 1.109375 -6.5625 L 1.109375 -8.421875 Z M 2.203125 -8.421875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-23">
<path style="stroke:none;" d="M 1.125 -9.125 L 2.203125 -9.125 L 2.203125 0 L 1.125 0 Z M 1.125 -9.125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-24">
<path style="stroke:none;" d="M 6.421875 -8.46875 L 6.421875 -7.3125 C 5.972656 -7.519531 5.546875 -7.675781 5.140625 -7.78125 C 4.742188 -7.894531 4.363281 -7.953125 4 -7.953125 C 3.351562 -7.953125 2.851562 -7.828125 2.5 -7.578125 C 2.15625 -7.328125 1.984375 -6.96875 1.984375 -6.5 C 1.984375 -6.113281 2.097656 -5.820312 2.328125 -5.625 C 2.554688 -5.425781 3 -5.269531 3.65625 -5.15625 L 4.359375 -5 C 5.242188 -4.832031 5.894531 -4.535156 6.3125 -4.109375 C 6.738281 -3.691406 6.953125 -3.128906 6.953125 -2.421875 C 6.953125 -1.566406 6.664062 -0.921875 6.09375 -0.484375 C 5.53125 -0.046875 4.695312 0.171875 3.59375 0.171875 C 3.1875 0.171875 2.75 0.125 2.28125 0.03125 C 1.8125 -0.0625 1.328125 -0.203125 0.828125 -0.390625 L 0.828125 -1.609375 C 1.304688 -1.335938 1.773438 -1.132812 2.234375 -1 C 2.703125 -0.863281 3.15625 -0.796875 3.59375 -0.796875 C 4.269531 -0.796875 4.789062 -0.925781 5.15625 -1.1875 C 5.53125 -1.457031 5.71875 -1.835938 5.71875 -2.328125 C 5.71875 -2.753906 5.582031 -3.085938 5.3125 -3.328125 C 5.050781 -3.578125 4.617188 -3.757812 4.015625 -3.875 L 3.296875 -4.015625 C 2.410156 -4.191406 1.769531 -4.46875 1.375 -4.84375 C 0.988281 -5.21875 0.796875 -5.738281 0.796875 -6.40625 C 0.796875 -7.1875 1.066406 -7.796875 1.609375 -8.234375 C 2.148438 -8.679688 2.898438 -8.90625 3.859375 -8.90625 C 4.273438 -8.90625 4.691406 -8.867188 5.109375 -8.796875 C 5.535156 -8.722656 5.972656 -8.613281 6.421875 -8.46875 Z M 6.421875 -8.46875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-25">
<path style="stroke:none;" d="M 0.5 -6.5625 L 1.578125 -6.5625 L 2.9375 -1.4375 L 4.265625 -6.5625 L 5.546875 -6.5625 L 6.890625 -1.4375 L 8.234375 -6.5625 L 9.3125 -6.5625 L 7.59375 0 L 6.328125 0 L 4.90625 -5.375 L 3.5 0 L 2.21875 0 Z M 0.5 -6.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-26">
<path style="stroke:none;" d="M 1.28125 -1.484375 L 2.515625 -1.484375 L 2.515625 0 L 1.28125 0 Z M 1.28125 -1.484375 "/>
</symbol>
<symbol overflow="visible" id="glyph0-27">
<path style="stroke:none;" d="M 6.125 2 L 6.125 2.828125 L -0.125 2.828125 L -0.125 2 Z M 6.125 2 "/>
</symbol>
<symbol overflow="visible" id="glyph1-0">
<path style="stroke:none;" d="M 0.40625 1.421875 L 0.40625 -5.640625 L 4.40625 -5.640625 L 4.40625 1.421875 Z M 0.84375 0.96875 L 3.953125 0.96875 L 3.953125 -5.1875 L 0.84375 -5.1875 Z M 0.84375 0.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-1">
<path style="stroke:none;" d="M 4.28125 -5.640625 L 4.28125 -4.875 C 3.976562 -5.019531 3.691406 -5.125 3.421875 -5.1875 C 3.160156 -5.257812 2.910156 -5.296875 2.671875 -5.296875 C 2.234375 -5.296875 1.898438 -5.210938 1.671875 -5.046875 C 1.441406 -4.878906 1.328125 -4.644531 1.328125 -4.34375 C 1.328125 -4.082031 1.398438 -3.882812 1.546875 -3.75 C 1.703125 -3.625 2 -3.519531 2.4375 -3.4375 L 2.90625 -3.34375 C 3.5 -3.226562 3.9375 -3.03125 4.21875 -2.75 C 4.5 -2.46875 4.640625 -2.085938 4.640625 -1.609375 C 4.640625 -1.046875 4.445312 -0.617188 4.0625 -0.328125 C 3.6875 -0.0351562 3.132812 0.109375 2.40625 0.109375 C 2.125 0.109375 1.828125 0.078125 1.515625 0.015625 C 1.203125 -0.046875 0.878906 -0.140625 0.546875 -0.265625 L 0.546875 -1.078125 C 0.867188 -0.890625 1.179688 -0.75 1.484375 -0.65625 C 1.796875 -0.570312 2.101562 -0.53125 2.40625 -0.53125 C 2.851562 -0.53125 3.195312 -0.617188 3.4375 -0.796875 C 3.6875 -0.972656 3.8125 -1.222656 3.8125 -1.546875 C 3.8125 -1.835938 3.722656 -2.0625 3.546875 -2.21875 C 3.367188 -2.382812 3.082031 -2.507812 2.6875 -2.59375 L 2.203125 -2.6875 C 1.609375 -2.800781 1.179688 -2.984375 0.921875 -3.234375 C 0.660156 -3.484375 0.53125 -3.832031 0.53125 -4.28125 C 0.53125 -4.789062 0.710938 -5.191406 1.078125 -5.484375 C 1.441406 -5.785156 1.941406 -5.9375 2.578125 -5.9375 C 2.847656 -5.9375 3.125 -5.910156 3.40625 -5.859375 C 3.695312 -5.816406 3.988281 -5.742188 4.28125 -5.640625 Z M 4.28125 -5.640625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-2">
<path style="stroke:none;" d="M 4.5 -2.375 L 4.5 -2.015625 L 1.1875 -2.015625 C 1.21875 -1.523438 1.367188 -1.148438 1.640625 -0.890625 C 1.910156 -0.628906 2.28125 -0.5 2.75 -0.5 C 3.03125 -0.5 3.300781 -0.53125 3.5625 -0.59375 C 3.820312 -0.664062 4.078125 -0.769531 4.328125 -0.90625 L 4.328125 -0.21875 C 4.066406 -0.113281 3.800781 -0.0351562 3.53125 0.015625 C 3.257812 0.078125 2.988281 0.109375 2.71875 0.109375 C 2.019531 0.109375 1.460938 -0.09375 1.046875 -0.5 C 0.640625 -0.90625 0.4375 -1.453125 0.4375 -2.140625 C 0.4375 -2.859375 0.628906 -3.425781 1.015625 -3.84375 C 1.410156 -4.269531 1.9375 -4.484375 2.59375 -4.484375 C 3.175781 -4.484375 3.640625 -4.289062 3.984375 -3.90625 C 4.328125 -3.53125 4.5 -3.019531 4.5 -2.375 Z M 3.78125 -2.578125 C 3.769531 -2.972656 3.65625 -3.285156 3.4375 -3.515625 C 3.226562 -3.753906 2.945312 -3.875 2.59375 -3.875 C 2.195312 -3.875 1.875 -3.757812 1.625 -3.53125 C 1.382812 -3.300781 1.25 -2.984375 1.21875 -2.578125 Z M 3.78125 -2.578125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-3">
<path style="stroke:none;" d="M 3.296875 -3.703125 C 3.210938 -3.753906 3.125 -3.789062 3.03125 -3.8125 C 2.9375 -3.832031 2.832031 -3.84375 2.71875 -3.84375 C 2.3125 -3.84375 2 -3.707031 1.78125 -3.4375 C 1.5625 -3.175781 1.453125 -2.800781 1.453125 -2.3125 L 1.453125 0 L 0.734375 0 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.703125 C 1.597656 -3.960938 1.789062 -4.15625 2.03125 -4.28125 C 2.28125 -4.414062 2.578125 -4.484375 2.921875 -4.484375 C 2.972656 -4.484375 3.023438 -4.476562 3.078125 -4.46875 C 3.140625 -4.46875 3.207031 -4.457031 3.28125 -4.4375 Z M 3.296875 -3.703125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-4">
<path style="stroke:none;" d="M 0.234375 -4.375 L 1 -4.375 L 2.375 -0.703125 L 3.734375 -4.375 L 4.5 -4.375 L 2.859375 0 L 1.875 0 Z M 0.234375 -4.375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-5">
<path style="stroke:none;" d="M 0.9375 -1 L 1.765625 -1 L 1.765625 0 L 0.9375 0 Z M 0.9375 -4.140625 L 1.765625 -4.140625 L 1.765625 -3.140625 L 0.9375 -3.140625 Z M 0.9375 -4.140625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-6">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph1-7">
<path style="stroke:none;" d="M 3.640625 -2.234375 C 3.640625 -2.753906 3.53125 -3.15625 3.3125 -3.4375 C 3.09375 -3.726562 2.789062 -3.875 2.40625 -3.875 C 2.019531 -3.875 1.71875 -3.726562 1.5 -3.4375 C 1.289062 -3.15625 1.1875 -2.753906 1.1875 -2.234375 C 1.1875 -1.722656 1.289062 -1.320312 1.5 -1.03125 C 1.71875 -0.75 2.019531 -0.609375 2.40625 -0.609375 C 2.789062 -0.609375 3.09375 -0.75 3.3125 -1.03125 C 3.53125 -1.320312 3.640625 -1.722656 3.640625 -2.234375 Z M 4.359375 -0.546875 C 4.359375 0.203125 4.191406 0.757812 3.859375 1.125 C 3.523438 1.488281 3.019531 1.671875 2.34375 1.671875 C 2.082031 1.671875 1.835938 1.648438 1.609375 1.609375 C 1.390625 1.566406 1.175781 1.507812 0.96875 1.4375 L 0.96875 0.734375 C 1.175781 0.847656 1.382812 0.929688 1.59375 0.984375 C 1.800781 1.046875 2.007812 1.078125 2.21875 1.078125 C 2.695312 1.078125 3.050781 0.953125 3.28125 0.703125 C 3.519531 0.453125 3.640625 0.078125 3.640625 -0.421875 L 3.640625 -0.765625 C 3.484375 -0.515625 3.289062 -0.320312 3.0625 -0.1875 C 2.832031 -0.0625 2.554688 0 2.234375 0 C 1.691406 0 1.253906 -0.203125 0.921875 -0.609375 C 0.597656 -1.023438 0.4375 -1.566406 0.4375 -2.234375 C 0.4375 -2.910156 0.597656 -3.453125 0.921875 -3.859375 C 1.253906 -4.273438 1.691406 -4.484375 2.234375 -4.484375 C 2.554688 -4.484375 2.832031 -4.414062 3.0625 -4.28125 C 3.289062 -4.15625 3.484375 -3.96875 3.640625 -3.71875 L 3.640625 -4.375 L 4.359375 -4.375 Z M 4.359375 -0.546875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-8">
<path style="stroke:none;" d="M 2.453125 -3.875 C 2.066406 -3.875 1.757812 -3.722656 1.53125 -3.421875 C 1.3125 -3.117188 1.203125 -2.707031 1.203125 -2.1875 C 1.203125 -1.664062 1.3125 -1.253906 1.53125 -0.953125 C 1.757812 -0.648438 2.066406 -0.5 2.453125 -0.5 C 2.835938 -0.5 3.140625 -0.648438 3.359375 -0.953125 C 3.585938 -1.253906 3.703125 -1.664062 3.703125 -2.1875 C 3.703125 -2.707031 3.585938 -3.117188 3.359375 -3.421875 C 3.140625 -3.722656 2.835938 -3.875 2.453125 -3.875 Z M 2.453125 -4.484375 C 3.078125 -4.484375 3.566406 -4.28125 3.921875 -3.875 C 4.273438 -3.46875 4.453125 -2.90625 4.453125 -2.1875 C 4.453125 -1.46875 4.273438 -0.90625 3.921875 -0.5 C 3.566406 -0.09375 3.078125 0.109375 2.453125 0.109375 C 1.828125 0.109375 1.332031 -0.09375 0.96875 -0.5 C 0.613281 -0.90625 0.4375 -1.46875 0.4375 -2.1875 C 0.4375 -2.90625 0.613281 -3.46875 0.96875 -3.875 C 1.332031 -4.28125 1.828125 -4.484375 2.453125 -4.484375 Z M 2.453125 -4.484375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-9">
<path style="stroke:none;" d="M 1.46875 -5.625 L 1.46875 -4.375 L 2.953125 -4.375 L 2.953125 -3.8125 L 1.46875 -3.8125 L 1.46875 -1.4375 C 1.46875 -1.082031 1.515625 -0.851562 1.609375 -0.75 C 1.710938 -0.65625 1.910156 -0.609375 2.203125 -0.609375 L 2.953125 -0.609375 L 2.953125 0 L 2.203125 0 C 1.648438 0 1.269531 -0.101562 1.0625 -0.3125 C 0.851562 -0.519531 0.75 -0.894531 0.75 -1.4375 L 0.75 -3.8125 L 0.21875 -3.8125 L 0.21875 -4.375 L 0.75 -4.375 L 0.75 -5.625 Z M 1.46875 -5.625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-10">
<path style="stroke:none;" d="M 3.90625 -4.203125 L 3.90625 -3.53125 C 3.695312 -3.644531 3.488281 -3.726562 3.28125 -3.78125 C 3.082031 -3.84375 2.878906 -3.875 2.671875 -3.875 C 2.203125 -3.875 1.835938 -3.722656 1.578125 -3.421875 C 1.328125 -3.128906 1.203125 -2.71875 1.203125 -2.1875 C 1.203125 -1.65625 1.328125 -1.238281 1.578125 -0.9375 C 1.835938 -0.644531 2.203125 -0.5 2.671875 -0.5 C 2.878906 -0.5 3.082031 -0.523438 3.28125 -0.578125 C 3.488281 -0.640625 3.695312 -0.722656 3.90625 -0.828125 L 3.90625 -0.171875 C 3.707031 -0.078125 3.5 -0.0078125 3.28125 0.03125 C 3.0625 0.0820312 2.832031 0.109375 2.59375 0.109375 C 1.9375 0.109375 1.410156 -0.09375 1.015625 -0.5 C 0.628906 -0.914062 0.4375 -1.476562 0.4375 -2.1875 C 0.4375 -2.894531 0.632812 -3.453125 1.03125 -3.859375 C 1.425781 -4.273438 1.960938 -4.484375 2.640625 -4.484375 C 2.859375 -4.484375 3.070312 -4.457031 3.28125 -4.40625 C 3.5 -4.363281 3.707031 -4.296875 3.90625 -4.203125 Z M 3.90625 -4.203125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-11">
<path style="stroke:none;" d="M 4.390625 -2.640625 L 4.390625 0 L 3.671875 0 L 3.671875 -2.625 C 3.671875 -3.03125 3.585938 -3.335938 3.421875 -3.546875 C 3.265625 -3.753906 3.023438 -3.859375 2.703125 -3.859375 C 2.316406 -3.859375 2.007812 -3.734375 1.78125 -3.484375 C 1.5625 -3.234375 1.453125 -2.894531 1.453125 -2.46875 L 1.453125 0 L 0.734375 0 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.703125 C 1.617188 -3.960938 1.816406 -4.15625 2.046875 -4.28125 C 2.285156 -4.414062 2.554688 -4.484375 2.859375 -4.484375 C 3.367188 -4.484375 3.75 -4.328125 4 -4.015625 C 4.257812 -3.703125 4.390625 -3.242188 4.390625 -2.640625 Z M 4.390625 -2.640625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-12">
<path style="stroke:none;" d="M 0.75 -4.375 L 1.46875 -4.375 L 1.46875 0 L 0.75 0 Z M 0.75 -6.078125 L 1.46875 -6.078125 L 1.46875 -5.171875 L 0.75 -5.171875 Z M 0.75 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-13">
<path style="stroke:none;" d="M 2.96875 -6.078125 L 2.96875 -5.484375 L 2.28125 -5.484375 C 2.019531 -5.484375 1.835938 -5.429688 1.734375 -5.328125 C 1.640625 -5.222656 1.59375 -5.035156 1.59375 -4.765625 L 1.59375 -4.375 L 2.78125 -4.375 L 2.78125 -3.8125 L 1.59375 -3.8125 L 1.59375 0 L 0.875 0 L 0.875 -3.8125 L 0.1875 -3.8125 L 0.1875 -4.375 L 0.875 -4.375 L 0.875 -4.6875 C 0.875 -5.164062 0.984375 -5.515625 1.203125 -5.734375 C 1.429688 -5.960938 1.796875 -6.078125 2.296875 -6.078125 Z M 2.96875 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-14">
<path style="stroke:none;" d="M 4.15625 -3.53125 C 4.34375 -3.851562 4.5625 -4.09375 4.8125 -4.25 C 5.0625 -4.40625 5.351562 -4.484375 5.6875 -4.484375 C 6.144531 -4.484375 6.492188 -4.320312 6.734375 -4 C 6.984375 -3.6875 7.109375 -3.234375 7.109375 -2.640625 L 7.109375 0 L 6.390625 0 L 6.390625 -2.625 C 6.390625 -3.039062 6.316406 -3.347656 6.171875 -3.546875 C 6.023438 -3.753906 5.796875 -3.859375 5.484375 -3.859375 C 5.117188 -3.859375 4.828125 -3.734375 4.609375 -3.484375 C 4.390625 -3.234375 4.28125 -2.894531 4.28125 -2.46875 L 4.28125 0 L 3.5625 0 L 3.5625 -2.625 C 3.5625 -3.039062 3.484375 -3.347656 3.328125 -3.546875 C 3.179688 -3.753906 2.957031 -3.859375 2.65625 -3.859375 C 2.28125 -3.859375 1.984375 -3.734375 1.765625 -3.484375 C 1.554688 -3.234375 1.453125 -2.894531 1.453125 -2.46875 L 1.453125 0 L 0.734375 0 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.703125 C 1.617188 -3.960938 1.816406 -4.15625 2.046875 -4.28125 C 2.273438 -4.414062 2.546875 -4.484375 2.859375 -4.484375 C 3.171875 -4.484375 3.4375 -4.398438 3.65625 -4.234375 C 3.882812 -4.078125 4.050781 -3.84375 4.15625 -3.53125 Z M 4.15625 -3.53125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-15">
<path style="stroke:none;" d="M 0.75 -6.078125 L 1.46875 -6.078125 L 1.46875 0 L 0.75 0 Z M 0.75 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-16">
<path style="stroke:none;" d="M 3.640625 -3.71875 L 3.640625 -6.078125 L 4.359375 -6.078125 L 4.359375 0 L 3.640625 0 L 3.640625 -0.65625 C 3.484375 -0.394531 3.289062 -0.203125 3.0625 -0.078125 C 2.832031 0.046875 2.554688 0.109375 2.234375 0.109375 C 1.703125 0.109375 1.269531 -0.0976562 0.9375 -0.515625 C 0.601562 -0.941406 0.4375 -1.5 0.4375 -2.1875 C 0.4375 -2.875 0.601562 -3.425781 0.9375 -3.84375 C 1.269531 -4.269531 1.703125 -4.484375 2.234375 -4.484375 C 2.554688 -4.484375 2.832031 -4.421875 3.0625 -4.296875 C 3.289062 -4.171875 3.484375 -3.976562 3.640625 -3.71875 Z M 1.1875 -2.1875 C 1.1875 -1.65625 1.296875 -1.238281 1.515625 -0.9375 C 1.734375 -0.632812 2.03125 -0.484375 2.40625 -0.484375 C 2.789062 -0.484375 3.09375 -0.632812 3.3125 -0.9375 C 3.53125 -1.238281 3.640625 -1.65625 3.640625 -2.1875 C 3.640625 -2.71875 3.53125 -3.128906 3.3125 -3.421875 C 3.09375 -3.722656 2.789062 -3.875 2.40625 -3.875 C 2.03125 -3.875 1.734375 -3.722656 1.515625 -3.421875 C 1.296875 -3.128906 1.1875 -2.71875 1.1875 -2.1875 Z M 1.1875 -2.1875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-17">
<path style="stroke:none;" d="M 3.890625 -2.1875 C 3.890625 -2.71875 3.78125 -3.128906 3.5625 -3.421875 C 3.351562 -3.722656 3.054688 -3.875 2.671875 -3.875 C 2.296875 -3.875 2 -3.722656 1.78125 -3.421875 C 1.5625 -3.128906 1.453125 -2.71875 1.453125 -2.1875 C 1.453125 -1.65625 1.5625 -1.238281 1.78125 -0.9375 C 2 -0.632812 2.296875 -0.484375 2.671875 -0.484375 C 3.054688 -0.484375 3.351562 -0.632812 3.5625 -0.9375 C 3.78125 -1.238281 3.890625 -1.65625 3.890625 -2.1875 Z M 1.453125 -3.71875 C 1.597656 -3.976562 1.785156 -4.171875 2.015625 -4.296875 C 2.253906 -4.421875 2.53125 -4.484375 2.84375 -4.484375 C 3.375 -4.484375 3.804688 -4.269531 4.140625 -3.84375 C 4.472656 -3.425781 4.640625 -2.875 4.640625 -2.1875 C 4.640625 -1.5 4.472656 -0.941406 4.140625 -0.515625 C 3.804688 -0.0976562 3.375 0.109375 2.84375 0.109375 C 2.53125 0.109375 2.253906 0.046875 2.015625 -0.078125 C 1.785156 -0.203125 1.597656 -0.394531 1.453125 -0.65625 L 1.453125 0 L 0.734375 0 L 0.734375 -6.078125 L 1.453125 -6.078125 Z M 1.453125 -3.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-18">
<path style="stroke:none;" d="M 0.859375 -1 L 1.6875 -1 L 1.6875 0 L 0.859375 0 Z M 0.859375 -1 "/>
</symbol>
<symbol overflow="visible" id="glyph1-19">
<path style="stroke:none;" d="M 0.78125 -5.828125 L 4.46875 -5.828125 L 4.46875 -5.171875 L 1.578125 -5.171875 L 1.578125 -3.4375 L 4.359375 -3.4375 L 4.359375 -2.78125 L 1.578125 -2.78125 L 1.578125 -0.671875 L 4.546875 -0.671875 L 4.546875 0 L 0.78125 0 Z M 0.78125 -5.828125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-20">
<path style="stroke:none;" d="M 4.390625 -4.375 L 2.8125 -2.25 L 4.46875 0 L 3.625 0 L 2.359375 -1.71875 L 1.078125 0 L 0.234375 0 L 1.9375 -2.296875 L 0.375 -4.375 L 1.21875 -4.375 L 2.390625 -2.8125 L 3.546875 -4.375 Z M 4.390625 -4.375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-21">
<path style="stroke:none;" d="M 0.6875 -1.734375 L 0.6875 -4.375 L 1.40625 -4.375 L 1.40625 -1.75 C 1.40625 -1.34375 1.484375 -1.035156 1.640625 -0.828125 C 1.804688 -0.617188 2.050781 -0.515625 2.375 -0.515625 C 2.757812 -0.515625 3.0625 -0.632812 3.28125 -0.875 C 3.507812 -1.125 3.625 -1.460938 3.625 -1.890625 L 3.625 -4.375 L 4.34375 -4.375 L 4.34375 0 L 3.625 0 L 3.625 -0.671875 C 3.445312 -0.410156 3.242188 -0.210938 3.015625 -0.078125 C 2.785156 0.046875 2.519531 0.109375 2.21875 0.109375 C 1.71875 0.109375 1.335938 -0.046875 1.078125 -0.359375 C 0.816406 -0.671875 0.6875 -1.128906 0.6875 -1.734375 Z M 2.484375 -4.484375 Z M 2.484375 -4.484375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-22">
<path style="stroke:none;" d="M 4.078125 1.328125 L 4.078125 1.890625 L -0.078125 1.890625 L -0.078125 1.328125 Z M 4.078125 1.328125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-23">
<path style="stroke:none;" d="M 3.546875 -4.25 L 3.546875 -3.5625 C 3.335938 -3.664062 3.125 -3.742188 2.90625 -3.796875 C 2.6875 -3.847656 2.460938 -3.875 2.234375 -3.875 C 1.878906 -3.875 1.609375 -3.816406 1.421875 -3.703125 C 1.242188 -3.597656 1.15625 -3.4375 1.15625 -3.21875 C 1.15625 -3.050781 1.21875 -2.921875 1.34375 -2.828125 C 1.476562 -2.734375 1.738281 -2.644531 2.125 -2.5625 L 2.375 -2.5 C 2.882812 -2.394531 3.242188 -2.242188 3.453125 -2.046875 C 3.671875 -1.847656 3.78125 -1.566406 3.78125 -1.203125 C 3.78125 -0.796875 3.617188 -0.472656 3.296875 -0.234375 C 2.972656 -0.00390625 2.53125 0.109375 1.96875 0.109375 C 1.738281 0.109375 1.492188 0.0820312 1.234375 0.03125 C 0.984375 -0.0078125 0.71875 -0.0703125 0.4375 -0.15625 L 0.4375 -0.90625 C 0.695312 -0.769531 0.957031 -0.664062 1.21875 -0.59375 C 1.476562 -0.519531 1.734375 -0.484375 1.984375 -0.484375 C 2.328125 -0.484375 2.585938 -0.539062 2.765625 -0.65625 C 2.953125 -0.78125 3.046875 -0.945312 3.046875 -1.15625 C 3.046875 -1.351562 2.976562 -1.503906 2.84375 -1.609375 C 2.707031 -1.710938 2.421875 -1.8125 1.984375 -1.90625 L 1.734375 -1.96875 C 1.285156 -2.0625 0.960938 -2.203125 0.765625 -2.390625 C 0.566406 -2.585938 0.46875 -2.851562 0.46875 -3.1875 C 0.46875 -3.601562 0.613281 -3.921875 0.90625 -4.140625 C 1.195312 -4.367188 1.609375 -4.484375 2.140625 -4.484375 C 2.410156 -4.484375 2.660156 -4.460938 2.890625 -4.421875 C 3.128906 -4.378906 3.347656 -4.320312 3.546875 -4.25 Z M 3.546875 -4.25 "/>
</symbol>
<symbol overflow="visible" id="glyph1-24">
<path style="stroke:none;" d="M 5.15625 -5.390625 L 5.15625 -4.546875 C 4.882812 -4.796875 4.597656 -4.984375 4.296875 -5.109375 C 4.003906 -5.234375 3.6875 -5.296875 3.34375 -5.296875 C 2.675781 -5.296875 2.164062 -5.085938 1.8125 -4.671875 C 1.457031 -4.265625 1.28125 -3.675781 1.28125 -2.90625 C 1.28125 -2.144531 1.457031 -1.554688 1.8125 -1.140625 C 2.164062 -0.734375 2.675781 -0.53125 3.34375 -0.53125 C 3.6875 -0.53125 4.003906 -0.59375 4.296875 -0.71875 C 4.597656 -0.84375 4.882812 -1.03125 5.15625 -1.28125 L 5.15625 -0.453125 C 4.875 -0.265625 4.578125 -0.125 4.265625 -0.03125 C 3.960938 0.0625 3.640625 0.109375 3.296875 0.109375 C 2.410156 0.109375 1.710938 -0.160156 1.203125 -0.703125 C 0.703125 -1.242188 0.453125 -1.976562 0.453125 -2.90625 C 0.453125 -3.84375 0.703125 -4.582031 1.203125 -5.125 C 1.710938 -5.664062 2.410156 -5.9375 3.296875 -5.9375 C 3.648438 -5.9375 3.976562 -5.890625 4.28125 -5.796875 C 4.59375 -5.703125 4.882812 -5.566406 5.15625 -5.390625 Z M 5.15625 -5.390625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-25">
<path style="stroke:none;" d="M 1.4375 -5.828125 L 1.4375 -3.671875 L 0.765625 -3.671875 L 0.765625 -5.828125 Z M 2.90625 -5.828125 L 2.90625 -3.671875 L 2.25 -3.671875 L 2.25 -5.828125 Z M 2.90625 -5.828125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-26">
<path style="stroke:none;" d="M 0.78125 -5.828125 L 1.578125 -5.828125 L 1.578125 -3.4375 L 4.4375 -3.4375 L 4.4375 -5.828125 L 5.234375 -5.828125 L 5.234375 0 L 4.4375 0 L 4.4375 -2.78125 L 1.578125 -2.78125 L 1.578125 0 L 0.78125 0 Z M 0.78125 -5.828125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-27">
<path style="stroke:none;" d="M 1.453125 -0.65625 L 1.453125 1.671875 L 0.734375 1.671875 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.71875 C 1.597656 -3.976562 1.785156 -4.171875 2.015625 -4.296875 C 2.253906 -4.421875 2.53125 -4.484375 2.84375 -4.484375 C 3.375 -4.484375 3.804688 -4.269531 4.140625 -3.84375 C 4.472656 -3.425781 4.640625 -2.875 4.640625 -2.1875 C 4.640625 -1.5 4.472656 -0.941406 4.140625 -0.515625 C 3.804688 -0.0976562 3.375 0.109375 2.84375 0.109375 C 2.53125 0.109375 2.253906 0.046875 2.015625 -0.078125 C 1.785156 -0.203125 1.597656 -0.394531 1.453125 -0.65625 Z M 3.890625 -2.1875 C 3.890625 -2.71875 3.78125 -3.128906 3.5625 -3.421875 C 3.351562 -3.722656 3.054688 -3.875 2.671875 -3.875 C 2.296875 -3.875 2 -3.722656 1.78125 -3.421875 C 1.5625 -3.128906 1.453125 -2.71875 1.453125 -2.1875 C 1.453125 -1.65625 1.5625 -1.238281 1.78125 -0.9375 C 2 -0.632812 2.296875 -0.484375 2.671875 -0.484375 C 3.054688 -0.484375 3.351562 -0.632812 3.5625 -0.9375 C 3.78125 -1.238281 3.890625 -1.65625 3.890625 -2.1875 Z M 3.890625 -2.1875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-28">
<path style="stroke:none;" d="M 2.75 -2.203125 C 2.164062 -2.203125 1.757812 -2.132812 1.53125 -2 C 1.3125 -1.863281 1.203125 -1.640625 1.203125 -1.328125 C 1.203125 -1.066406 1.285156 -0.859375 1.453125 -0.703125 C 1.617188 -0.554688 1.847656 -0.484375 2.140625 -0.484375 C 2.535156 -0.484375 2.851562 -0.625 3.09375 -0.90625 C 3.332031 -1.195312 3.453125 -1.578125 3.453125 -2.046875 L 3.453125 -2.203125 Z M 4.171875 -2.5 L 4.171875 0 L 3.453125 0 L 3.453125 -0.671875 C 3.296875 -0.398438 3.09375 -0.203125 2.84375 -0.078125 C 2.601562 0.046875 2.304688 0.109375 1.953125 0.109375 C 1.503906 0.109375 1.144531 -0.015625 0.875 -0.265625 C 0.613281 -0.515625 0.484375 -0.851562 0.484375 -1.28125 C 0.484375 -1.769531 0.644531 -2.140625 0.96875 -2.390625 C 1.300781 -2.640625 1.796875 -2.765625 2.453125 -2.765625 L 3.453125 -2.765625 L 3.453125 -2.828125 C 3.453125 -3.160156 3.34375 -3.414062 3.125 -3.59375 C 2.914062 -3.78125 2.613281 -3.875 2.21875 -3.875 C 1.96875 -3.875 1.722656 -3.84375 1.484375 -3.78125 C 1.242188 -3.71875 1.015625 -3.628906 0.796875 -3.515625 L 0.796875 -4.171875 C 1.066406 -4.273438 1.320312 -4.351562 1.5625 -4.40625 C 1.8125 -4.457031 2.054688 -4.484375 2.296875 -4.484375 C 2.921875 -4.484375 3.390625 -4.316406 3.703125 -3.984375 C 4.015625 -3.660156 4.171875 -3.164062 4.171875 -2.5 Z M 4.171875 -2.5 "/>
</symbol>
</g>
<clipPath id="clip1">
<path d="M 588.042969 6 L 597.65625 6 L 597.65625 19 L 588.042969 19 Z M 588.042969 6 "/>
</clipPath>
<clipPath id="clip2">
<path d="M 731 6 L 733.875 6 L 733.875 19 L 731 19 Z M 731 6 "/>
</clipPath>
<clipPath id="clip3">
<path d="M 974.046875 6 L 978.160156 6 L 978.160156 19 L 974.046875 19 Z M 974.046875 6 "/>
</clipPath>
<clipPath id="clip4">
<path d="M 1003.039062 6 L 1004.738281 6 L 1004.738281 19 L 1003.039062 19 Z M 1003.039062 6 "/>
</clipPath>
<clipPath id="clip5">
<path d="M 1176 6 L 1179.425781 6 L 1179.425781 19 L 1176 19 Z M 1176 6 "/>
</clipPath>
<clipPath id="clip6">
<path d="M 1198.246094 6 L 1229.460938 6 L 1229.460938 19 L 1198.246094 19 Z M 1198.246094 6 "/>
</clipPath>
<clipPath id="clip7">
<path d="M 580.164062 49 L 628 49 L 628 59 L 580.164062 59 Z M 580.164062 49 "/>
</clipPath>
<clipPath id="clip8">
<path d="M 980 48 L 996.039062 48 L 996.039062 59 L 980 59 Z M 980 48 "/>
</clipPath>
<clipPath id="clip9">
<path d="M 1006 48 L 1006.328125 48 L 1006.328125 59 L 1006 59 Z M 1006 48 "/>
</clipPath>
<clipPath id="clip10">
<path d="M 1016.539062 48 L 1058 48 L 1058 59 L 1016.539062 59 Z M 1016.539062 48 "/>
</clipPath>
<clipPath id="clip11">
<path d="M 1067.246094 48 L 1098 48 L 1098 59 L 1067.246094 59 Z M 1067.246094 48 "/>
</clipPath>
<clipPath id="clip12">
<path d="M 1174.039062 48 L 1228.273438 48 L 1228.273438 61 L 1174.039062 61 Z M 1174.039062 48 "/>
</clipPath>
<clipPath id="clip13">
<path d="M 1236 48 L 1244.597656 48 L 1244.597656 59 L 1236 59 Z M 1236 48 "/>
</clipPath>
<clipPath id="clip14">
<path d="M 1257 48 L 1262.09375 48 L 1262.09375 59 L 1257 59 Z M 1257 48 "/>
</clipPath>
<clipPath id="clip15">
<path d="M 608 81 L 630.199219 81 L 630.199219 93 L 608 93 Z M 608 81 "/>
</clipPath>
<clipPath id="clip16">
<path d="M 939.054688 81 L 968.039062 81 L 968.039062 93 L 939.054688 93 Z M 939.054688 81 "/>
</clipPath>
<clipPath id="clip17">
<path d="M 970.039062 81 L 1011 81 L 1011 93 L 970.039062 93 Z M 970.039062 81 "/>
</clipPath>
<clipPath id="clip18">
<path d="M 759.25 112 L 797 112 L 797 123 L 759.25 123 Z M 759.25 112 "/>
</clipPath>
<clipPath id="clip19">
<path d="M 673.429688 101 L 811 101 L 811 110 L 673.429688 110 Z M 673.429688 101 "/>
</clipPath>
<clipPath id="clip20">
<path d="M 1170 69 L 1193.144531 69 L 1193.144531 77 L 1170 77 Z M 1170 69 "/>
</clipPath>
<clipPath id="clip21">
<path d="M 152.046875 71 L 195 71 L 195 78 L 152.046875 78 Z M 152.046875 71 "/>
</clipPath>
<clipPath id="clip22">
<path d="M 199.238281 37 L 311 37 L 311 46 L 199.238281 46 Z M 199.238281 37 "/>
</clipPath>
<clipPath id="clip23">
<path d="M 1030.417969 37 L 1062.695312 37 L 1062.695312 45 L 1030.417969 45 Z M 1030.417969 37 "/>
</clipPath>
<clipPath id="clip24">
<path d="M 1168.152344 37 L 1209 37 L 1209 45 L 1168.152344 45 Z M 1168.152344 37 "/>
</clipPath>
<clipPath id="clip25">
<path d="M 1234 38 L 1245.207031 38 L 1245.207031 46 L 1234 46 Z M 1234 38 "/>
</clipPath>
</defs>
<g id="surface2">
<rect x="0" y="0" width="1280" height="152" style="fill:rgb(90%,90%,90%);fill-opacity:1;stroke:none;"/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(90%,90%,45%);fill-opacity:1;" d="M 586.042969 4 L 595.855469 4 L 595.855469 132 L 586.042969 132 Z M 586.042969 4 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(90%,90%,45%);fill-opacity:1;" d="M 728.445312 4 L 732.074219 4 L 732.074219 132 L 728.445312 132 Z M 728.445312 4 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(90%,90%,45%);fill-opacity:1;" d="M 972.046875 4 L 976.359375 4 L 976.359375 132 L 972.046875 132 Z M 972.046875 4 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(90%,90%,45%);fill-opacity:1;" d="M 1001.039062 4 L 1002.9375 4 L 1002.9375 132 L 1001.039062 132 Z M 1001.039062 4 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(90%,90%,45%);fill-opacity:1;" d="M 1173.941406 4 L 1177.625 4 L 1177.625 132 L 1173.941406 132 Z M 1173.941406 4 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(90%,90%,45%);fill-opacity:1;" d="M 1196.246094 4 L 1227.660156 4 L 1227.660156 132 L 1196.246094 132 Z M 1196.246094 4 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 4 0 L 4 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 46.1875 0 L 46.1875 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 88.371094 0 L 88.371094 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 130.558594 0 L 130.558594 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 172.742188 0 L 172.742188 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 214.929688 0 L 214.929688 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 257.113281 0 L 257.113281 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 299.300781 0 L 299.300781 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 341.484375 0 L 341.484375 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 383.671875 0 L 383.671875 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 425.855469 0 L 425.855469 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 468.042969 0 L 468.042969 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 510.226562 0 L 510.226562 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 552.414062 0 L 552.414062 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 594.601562 0 L 594.601562 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 636.785156 0 L 636.785156 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 678.972656 0 L 678.972656 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 721.15625 0 L 721.15625 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 763.34375 0 L 763.34375 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 805.527344 0 L 805.527344 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 847.714844 0 L 847.714844 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 889.898438 0 L 889.898438 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 932.085938 0 L 932.085938 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 974.269531 0 L 974.269531 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1016.457031 0 L 1016.457031 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1058.640625 0 L 1058.640625 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1100.828125 0 L 1100.828125 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1143.015625 0 L 1143.015625 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1185.199219 0 L 1185.199219 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1227.386719 0 L 1227.386719 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1269.570312 0 L 1269.570312 152 "/>
<g style="fill:rgb(40%,40%,40%);fill-opacity:1;">
<use xlink:href="#glyph0-1" x="4" y="147.503906"/>
<use xlink:href="#glyph0-2" x="11.582031" y="147.503906"/>
<use xlink:href="#glyph0-3" x="18.935547" y="147.503906"/>
<use xlink:href="#glyph0-4" x="25.533203" y="147.503906"/>
<use xlink:href="#glyph0-5" x="33.138672" y="147.503906"/>
<use xlink:href="#glyph0-6" x="36.953125" y="147.503906"/>
<use xlink:href="#glyph0-7" x="44.570312" y="147.503906"/>
<use xlink:href="#glyph0-8" x="49.503906" y="147.503906"/>
<use xlink:href="#glyph0-9" x="52.837891" y="147.503906"/>
<use xlink:href="#glyph0-5" x="60.455078" y="147.503906"/>
<use xlink:href="#glyph0-9" x="64.269531" y="147.503906"/>
<use xlink:href="#glyph0-8" x="71.886719" y="147.503906"/>
<use xlink:href="#glyph0-10" x="75.220703" y="147.503906"/>
<use xlink:href="#glyph0-8" x="82.322266" y="147.503906"/>
<use xlink:href="#glyph0-11" x="85.65625" y="147.503906"/>
<use xlink:href="#glyph0-8" x="91.908203" y="147.503906"/>
<use xlink:href="#glyph0-12" x="95.242188" y="147.503906"/>
<use xlink:href="#glyph0-13" x="102.583984" y="147.503906"/>
<use xlink:href="#glyph0-14" x="110.189453" y="147.503906"/>
<use xlink:href="#glyph0-5" x="114.232422" y="147.503906"/>
<use xlink:href="#glyph0-15" x="118.046875" y="147.503906"/>
<use xlink:href="#glyph0-16" x="125.681641" y="147.503906"/>
<use xlink:href="#glyph0-5" x="133.316406" y="147.503906"/>
<use xlink:href="#glyph0-17" x="137.130859" y="147.503906"/>
<use xlink:href="#glyph0-11" x="144.736328" y="147.503906"/>
</g>
<g clip-path="url(#clip1)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="588.042969" y="16"/>
<use xlink:href="#glyph0-17" x="594.294922" y="16"/>
<use xlink:href="#glyph0-11" x="601.900391" y="16"/>
<use xlink:href="#glyph0-18" x="608.152344" y="16"/>
<use xlink:href="#glyph0-19" x="615.769531" y="16"/>
<use xlink:href="#glyph0-13" x="623.152344" y="16"/>
<use xlink:href="#glyph0-9" x="630.757812" y="16"/>
<use xlink:href="#glyph0-20" x="638.375" y="16"/>
<use xlink:href="#glyph0-9" x="642.705078" y="16"/>
<use xlink:href="#glyph0-12" x="650.322266" y="16"/>
<use xlink:href="#glyph0-21" x="657.664062" y="16"/>
<use xlink:href="#glyph0-2" x="669.353516" y="16"/>
<use xlink:href="#glyph0-8" x="676.707031" y="16"/>
<use xlink:href="#glyph0-13" x="680.041016" y="16"/>
</g>
</g>
<g clip-path="url(#clip2)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="730.445312" y="16"/>
<use xlink:href="#glyph0-17" x="736.697266" y="16"/>
<use xlink:href="#glyph0-11" x="744.302734" y="16"/>
<use xlink:href="#glyph0-18" x="750.554688" y="16"/>
<use xlink:href="#glyph0-19" x="758.171875" y="16"/>
<use xlink:href="#glyph0-13" x="765.554688" y="16"/>
<use xlink:href="#glyph0-9" x="773.160156" y="16"/>
<use xlink:href="#glyph0-20" x="780.777344" y="16"/>
<use xlink:href="#glyph0-9" x="785.107422" y="16"/>
<use xlink:href="#glyph0-12" x="792.724609" y="16"/>
<use xlink:href="#glyph0-21" x="800.066406" y="16"/>
<use xlink:href="#glyph0-2" x="811.755859" y="16"/>
<use xlink:href="#glyph0-8" x="819.109375" y="16"/>
<use xlink:href="#glyph0-13" x="822.443359" y="16"/>
</g>
</g>
<g clip-path="url(#clip3)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="974.046875" y="16"/>
<use xlink:href="#glyph0-17" x="980.298828" y="16"/>
<use xlink:href="#glyph0-11" x="987.904297" y="16"/>
<use xlink:href="#glyph0-18" x="994.15625" y="16"/>
<use xlink:href="#glyph0-19" x="1001.773438" y="16"/>
<use xlink:href="#glyph0-13" x="1009.15625" y="16"/>
<use xlink:href="#glyph0-9" x="1016.761719" y="16"/>
<use xlink:href="#glyph0-20" x="1024.378906" y="16"/>
<use xlink:href="#glyph0-9" x="1028.708984" y="16"/>
<use xlink:href="#glyph0-12" x="1036.326172" y="16"/>
<use xlink:href="#glyph0-21" x="1043.667969" y="16"/>
<use xlink:href="#glyph0-2" x="1055.357422" y="16"/>
<use xlink:href="#glyph0-8" x="1062.710938" y="16"/>
<use xlink:href="#glyph0-13" x="1066.044922" y="16"/>
</g>
</g>
<g clip-path="url(#clip4)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="1003.039062" y="16"/>
<use xlink:href="#glyph0-17" x="1009.291016" y="16"/>
<use xlink:href="#glyph0-11" x="1016.896484" y="16"/>
<use xlink:href="#glyph0-18" x="1023.148438" y="16"/>
<use xlink:href="#glyph0-19" x="1030.765625" y="16"/>
<use xlink:href="#glyph0-13" x="1038.148438" y="16"/>
<use xlink:href="#glyph0-9" x="1045.753906" y="16"/>
<use xlink:href="#glyph0-20" x="1053.371094" y="16"/>
<use xlink:href="#glyph0-9" x="1057.701172" y="16"/>
<use xlink:href="#glyph0-12" x="1065.318359" y="16"/>
<use xlink:href="#glyph0-21" x="1072.660156" y="16"/>
<use xlink:href="#glyph0-2" x="1084.349609" y="16"/>
<use xlink:href="#glyph0-8" x="1091.703125" y="16"/>
<use xlink:href="#glyph0-13" x="1095.037109" y="16"/>
</g>
</g>
<g clip-path="url(#clip5)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="1175.941406" y="16"/>
<use xlink:href="#glyph0-17" x="1182.193359" y="16"/>
<use xlink:href="#glyph0-11" x="1189.798828" y="16"/>
<use xlink:href="#glyph0-18" x="1196.050781" y="16"/>
<use xlink:href="#glyph0-19" x="1203.667969" y="16"/>
<use xlink:href="#glyph0-13" x="1211.050781" y="16"/>
<use xlink:href="#glyph0-9" x="1218.65625" y="16"/>
<use xlink:href="#glyph0-20" x="1226.273438" y="16"/>
<use xlink:href="#glyph0-9" x="1230.603516" y="16"/>
<use xlink:href="#glyph0-12" x="1238.220703" y="16"/>
<use xlink:href="#glyph0-21" x="1245.5625" y="16"/>
<use xlink:href="#glyph0-2" x="1257.251953" y="16"/>
<use xlink:href="#glyph0-8" x="1264.605469" y="16"/>
<use xlink:href="#glyph0-13" x="1267.939453" y="16"/>
</g>
</g>
<g clip-path="url(#clip6)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="1198.246094" y="16"/>
<use xlink:href="#glyph0-17" x="1204.498047" y="16"/>
<use xlink:href="#glyph0-11" x="1212.103516" y="16"/>
<use xlink:href="#glyph0-18" x="1218.355469" y="16"/>
<use xlink:href="#glyph0-19" x="1225.972656" y="16"/>
<use xlink:href="#glyph0-13" x="1233.355469" y="16"/>
<use xlink:href="#glyph0-9" x="1240.960938" y="16"/>
<use xlink:href="#glyph0-20" x="1248.578125" y="16"/>
<use xlink:href="#glyph0-9" x="1252.908203" y="16"/>
<use xlink:href="#glyph0-12" x="1260.525391" y="16"/>
<use xlink:href="#glyph0-21" x="1267.867188" y="16"/>
<use xlink:href="#glyph0-2" x="1279.556641" y="16"/>
<use xlink:href="#glyph0-8" x="1286.910156" y="16"/>
<use xlink:href="#glyph0-13" x="1290.244141" y="16"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 5.941406 46 L 13.273438 46 L 13.273438 60 L 5.941406 60 Z M 5.941406 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 13.273438 46 L 138.445312 46 L 138.445312 60 L 13.273438 60 Z M 13.273438 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 138.445312 46 L 169.574219 46 L 169.574219 60 L 138.445312 60 Z M 138.445312 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 169.574219 46 L 173.832031 46 L 173.832031 60 L 169.574219 60 Z M 169.574219 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 173.832031 46 L 252.480469 46 L 252.480469 60 L 173.832031 60 Z M 173.832031 46 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-22" x="175.832031" y="58"/>
<use xlink:href="#glyph0-7" x="180.537109" y="58"/>
<use xlink:href="#glyph0-2" x="185.470703" y="58"/>
<use xlink:href="#glyph0-3" x="192.824219" y="58"/>
<use xlink:href="#glyph0-19" x="199.421875" y="58"/>
<use xlink:href="#glyph0-23" x="206.804688" y="58"/>
<use xlink:href="#glyph0-13" x="210.138672" y="58"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 252.476562 46 L 578.164062 46 L 578.164062 60 L 252.476562 60 Z M 252.476562 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 578.164062 46 L 732.695312 46 L 732.695312 60 L 578.164062 60 Z M 578.164062 46 "/>
<g clip-path="url(#clip7)" clip-rule="nonzero">
<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
<use xlink:href="#glyph0-3" x="580.164062" y="58"/>
<use xlink:href="#glyph0-12" x="586.761719" y="58"/>
<use xlink:href="#glyph0-13" x="594.103516" y="58"/>
<use xlink:href="#glyph0-13" x="601.708984" y="58"/>
<use xlink:href="#glyph0-19" x="609.314453" y="58"/>
<use xlink:href="#glyph0-3" x="616.697266" y="58"/>
<use xlink:href="#glyph0-22" x="623.294922" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 732.695312 46 L 755.976562 46 L 755.976562 60 L 732.695312 60 Z M 732.695312 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 755.976562 46 L 976.964844 46 L 976.964844 60 L 755.976562 60 Z M 755.976562 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 976.964844 46 L 977.816406 46 L 977.816406 60 L 976.964844 60 Z M 976.964844 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 977.820312 46 L 996.039062 46 L 996.039062 60 L 977.820312 60 Z M 977.820312 46 "/>
<g clip-path="url(#clip8)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-7" x="979.820312" y="58"/>
<use xlink:href="#glyph0-19" x="984.753906" y="58"/>
<use xlink:href="#glyph0-2" x="992.136719" y="58"/>
<use xlink:href="#glyph0-9" x="999.490234" y="58"/>
<use xlink:href="#glyph0-10" x="1007.107422" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 996.039062 46 L 999.808594 46 L 999.808594 60 L 996.039062 60 Z M 996.039062 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 999.808594 46 L 1003.238281 46 L 1003.238281 60 L 999.808594 60 Z M 999.808594 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1003.238281 46 L 1003.773438 46 L 1003.773438 60 L 1003.238281 60 Z M 1003.238281 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1003.777344 46 L 1006.328125 46 L 1006.328125 60 L 1003.777344 60 Z M 1003.777344 46 "/>
<g clip-path="url(#clip9)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-7" x="1005.777344" y="58"/>
<use xlink:href="#glyph0-19" x="1010.710938" y="58"/>
<use xlink:href="#glyph0-2" x="1018.09375" y="58"/>
<use xlink:href="#glyph0-9" x="1025.447266" y="58"/>
<use xlink:href="#glyph0-10" x="1033.064453" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1006.328125 46 L 1014.539062 46 L 1014.539062 60 L 1006.328125 60 Z M 1006.328125 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1014.539062 46 L 1058.179688 46 L 1058.179688 60 L 1014.539062 60 Z M 1014.539062 46 "/>
<g clip-path="url(#clip10)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-22" x="1016.539062" y="58"/>
<use xlink:href="#glyph0-7" x="1021.244141" y="58"/>
<use xlink:href="#glyph0-2" x="1026.177734" y="58"/>
<use xlink:href="#glyph0-3" x="1033.53125" y="58"/>
<use xlink:href="#glyph0-19" x="1040.128906" y="58"/>
<use xlink:href="#glyph0-23" x="1047.511719" y="58"/>
<use xlink:href="#glyph0-13" x="1050.845703" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1058.179688 46 L 1065.246094 46 L 1065.246094 60 L 1058.179688 60 Z M 1058.179688 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1065.246094 46 L 1156.160156 46 L 1156.160156 60 L 1065.246094 60 Z M 1065.246094 46 "/>
<g clip-path="url(#clip11)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-3" x="1067.246094" y="58"/>
<use xlink:href="#glyph0-23" x="1073.84375" y="58"/>
<use xlink:href="#glyph0-12" x="1077.177734" y="58"/>
<use xlink:href="#glyph0-11" x="1084.519531" y="58"/>
<use xlink:href="#glyph0-19" x="1090.771484" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1156.160156 46 L 1172.039062 46 L 1172.039062 60 L 1156.160156 60 Z M 1156.160156 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1172.039062 46 L 1228.273438 46 L 1228.273438 60 L 1172.039062 60 Z M 1172.039062 46 "/>
<g clip-path="url(#clip12)" clip-rule="nonzero">
<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
<use xlink:href="#glyph0-24" x="1174.039062" y="58"/>
<use xlink:href="#glyph0-25" x="1181.65625" y="58"/>
<use xlink:href="#glyph0-8" x="1191.470703" y="58"/>
<use xlink:href="#glyph0-22" x="1194.804688" y="58"/>
<use xlink:href="#glyph0-3" x="1199.509766" y="58"/>
<use xlink:href="#glyph0-4" x="1206.107422" y="58"/>
<use xlink:href="#glyph0-26" x="1213.712891" y="58"/>
<use xlink:href="#glyph0-2" x="1217.527344" y="58"/>
<use xlink:href="#glyph0-25" x="1224.880859" y="58"/>
<use xlink:href="#glyph0-2" x="1234.695312" y="58"/>
<use xlink:href="#glyph0-8" x="1242.048828" y="58"/>
<use xlink:href="#glyph0-22" x="1245.382812" y="58"/>
<use xlink:href="#glyph0-27" x="1250.087891" y="58"/>
<use xlink:href="#glyph0-8" x="1256.087891" y="58"/>
<use xlink:href="#glyph0-9" x="1259.421875" y="58"/>
<use xlink:href="#glyph0-23" x="1267.039062" y="58"/>
<use xlink:href="#glyph0-19" x="1270.373047" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1228.269531 46 L 1233.5625 46 L 1233.5625 60 L 1228.269531 60 Z M 1228.269531 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1233.566406 46 L 1244.597656 46 L 1244.597656 60 L 1233.566406 60 Z M 1233.566406 46 "/>
<g clip-path="url(#clip13)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-3" x="1235.566406" y="58"/>
<use xlink:href="#glyph0-23" x="1242.164062" y="58"/>
<use xlink:href="#glyph0-12" x="1245.498047" y="58"/>
<use xlink:href="#glyph0-11" x="1252.839844" y="58"/>
<use xlink:href="#glyph0-19" x="1259.091797" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1244.597656 46 L 1254.722656 46 L 1254.722656 60 L 1244.597656 60 Z M 1244.597656 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1254.722656 46 L 1262.09375 46 L 1262.09375 60 L 1254.722656 60 Z M 1254.722656 46 "/>
<g clip-path="url(#clip14)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-3" x="1256.722656" y="58"/>
<use xlink:href="#glyph0-23" x="1263.320312" y="58"/>
<use xlink:href="#glyph0-12" x="1266.654297" y="58"/>
<use xlink:href="#glyph0-11" x="1273.996094" y="58"/>
<use xlink:href="#glyph0-19" x="1280.248047" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1262.089844 46 L 1247.964844 46 L 1247.964844 60 L 1262.089844 60 Z M 1262.089844 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 138.445312 78 L 139.664062 78 L 139.664062 92 L 138.445312 92 Z M 138.445312 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 139.664062 78 L 166.859375 78 L 166.859375 92 L 139.664062 92 Z M 139.664062 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 166.863281 78 L 603.738281 78 L 603.738281 92 L 166.863281 92 Z M 166.863281 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 603.738281 78 L 605.601562 78 L 605.601562 92 L 603.738281 92 Z M 603.738281 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 605.601562 78 L 630.199219 78 L 630.199219 92 L 605.601562 92 Z M 605.601562 78 "/>
<g clip-path="url(#clip15)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-2" x="607.601562" y="90"/>
<use xlink:href="#glyph0-3" x="614.955078" y="90"/>
<use xlink:href="#glyph0-3" x="621.552734" y="90"/>
<use xlink:href="#glyph0-19" x="628.150391" y="90"/>
<use xlink:href="#glyph0-18" x="635.533203" y="90"/>
<use xlink:href="#glyph0-22" x="643.150391" y="90"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 630.199219 78 L 646.222656 78 L 646.222656 92 L 630.199219 92 Z M 630.199219 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 646.222656 78 L 723.402344 78 L 723.402344 92 L 646.222656 92 Z M 646.222656 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 723.402344 78 L 726.738281 78 L 726.738281 92 L 723.402344 92 Z M 723.402344 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 726.738281 78 L 935.714844 78 L 935.714844 92 L 726.738281 92 Z M 726.738281 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 935.714844 78 L 937.054688 78 L 937.054688 92 L 935.714844 92 Z M 935.714844 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 937.054688 78 L 968.039062 78 L 968.039062 92 L 937.054688 92 Z M 937.054688 78 "/>
<g clip-path="url(#clip16)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-2" x="939.054688" y="90"/>
<use xlink:href="#glyph0-3" x="946.408203" y="90"/>
<use xlink:href="#glyph0-3" x="953.005859" y="90"/>
<use xlink:href="#glyph0-19" x="959.603516" y="90"/>
<use xlink:href="#glyph0-18" x="966.986328" y="90"/>
<use xlink:href="#glyph0-22" x="974.603516" y="90"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 968.039062 78 L 1178.621094 78 L 1178.621094 92 L 968.039062 92 Z M 968.039062 78 "/>
<g clip-path="url(#clip17)" clip-rule="nonzero">
<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
<use xlink:href="#glyph0-2" x="970.039062" y="90"/>
<use xlink:href="#glyph0-3" x="977.392578" y="90"/>
<use xlink:href="#glyph0-3" x="983.990234" y="90"/>
<use xlink:href="#glyph0-19" x="990.587891" y="90"/>
<use xlink:href="#glyph0-18" x="997.970703" y="90"/>
<use xlink:href="#glyph0-22" x="1005.587891" y="90"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1178.621094 78 L 1179.300781 78 L 1179.300781 92 L 1178.621094 92 Z M 1178.621094 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1179.300781 78 L 1228.269531 78 L 1228.269531 92 L 1179.300781 92 Z M 1179.300781 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1228.269531 78 L 1195.445312 78 L 1195.445312 92 L 1228.269531 92 Z M 1228.269531 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 646.222656 110 L 647.367188 110 L 647.367188 124 L 646.222656 124 Z M 646.222656 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 647.367188 110 L 658.933594 110 L 658.933594 124 L 647.367188 124 Z M 647.367188 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 658.933594 110 L 703.359375 110 L 703.359375 124 L 658.933594 124 Z M 658.933594 110 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-22" x="660.933594" y="122"/>
<use xlink:href="#glyph0-7" x="665.638672" y="122"/>
<use xlink:href="#glyph0-2" x="670.572266" y="122"/>
<use xlink:href="#glyph0-3" x="677.925781" y="122"/>
<use xlink:href="#glyph0-19" x="684.523438" y="122"/>
<use xlink:href="#glyph0-23" x="691.90625" y="122"/>
<use xlink:href="#glyph0-13" x="695.240234" y="122"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 703.359375 110 L 721.449219 110 L 721.449219 124 L 703.359375 124 Z M 703.359375 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 721.449219 110 L 756.730469 110 L 756.730469 124 L 721.449219 124 Z M 721.449219 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 756.726562 110 L 757.25 110 L 757.25 124 L 756.726562 124 Z M 756.726562 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 757.25 110 L 828.3125 110 L 828.3125 124 L 757.25 124 Z M 757.25 110 "/>
<g clip-path="url(#clip18)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-25" x="759.25" y="122"/>
<use xlink:href="#glyph0-7" x="769.064453" y="122"/>
<use xlink:href="#glyph0-8" x="773.998047" y="122"/>
<use xlink:href="#glyph0-22" x="777.332031" y="122"/>
<use xlink:href="#glyph0-19" x="782.037109" y="122"/>
<use xlink:href="#glyph0-10" x="789.419922" y="122"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 828.3125 110 L 833.761719 110 L 833.761719 124 L 828.3125 124 Z M 828.3125 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 833.761719 110 L 931.601562 110 L 931.601562 124 L 833.761719 124 Z M 833.761719 110 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-3" x="835.761719" y="122"/>
<use xlink:href="#glyph0-23" x="842.359375" y="122"/>
<use xlink:href="#glyph0-12" x="845.693359" y="122"/>
<use xlink:href="#glyph0-11" x="853.035156" y="122"/>
<use xlink:href="#glyph0-19" x="859.287109" y="122"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 931.605469 110 L 935.714844 110 L 935.714844 124 L 931.605469 124 Z M 931.605469 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 935.714844 110 L 934.558594 110 L 934.558594 124 L 935.714844 124 Z M 935.714844 110 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 671.429688 113 L 671.429688 107 "/>
<g clip-path="url(#clip19)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="673.429688" y="108"/>
<use xlink:href="#glyph1-2" x="678.507812" y="108"/>
<use xlink:href="#glyph1-3" x="683.429688" y="108"/>
<use xlink:href="#glyph1-4" x="686.71875" y="108"/>
<use xlink:href="#glyph1-2" x="691.453125" y="108"/>
<use xlink:href="#glyph1-3" x="696.375" y="108"/>
<use xlink:href="#glyph1-5" x="699.664062" y="108"/>
<use xlink:href="#glyph1-6" x="702.359375" y="108"/>
<use xlink:href="#glyph1-7" x="704.902344" y="108"/>
<use xlink:href="#glyph1-8" x="709.980469" y="108"/>
<use xlink:href="#glyph1-9" x="714.875" y="108"/>
<use xlink:href="#glyph1-6" x="718.011719" y="108"/>
<use xlink:href="#glyph1-10" x="720.554688" y="108"/>
<use xlink:href="#glyph1-8" x="724.953125" y="108"/>
<use xlink:href="#glyph1-11" x="729.847656" y="108"/>
<use xlink:href="#glyph1-11" x="734.917969" y="108"/>
<use xlink:href="#glyph1-2" x="739.988281" y="108"/>
<use xlink:href="#glyph1-10" x="744.910156" y="108"/>
<use xlink:href="#glyph1-9" x="749.308594" y="108"/>
<use xlink:href="#glyph1-12" x="752.445312" y="108"/>
<use xlink:href="#glyph1-8" x="754.667969" y="108"/>
<use xlink:href="#glyph1-11" x="759.5625" y="108"/>
<use xlink:href="#glyph1-6" x="764.632812" y="108"/>
<use xlink:href="#glyph1-13" x="767.175781" y="108"/>
<use xlink:href="#glyph1-3" x="769.992188" y="108"/>
<use xlink:href="#glyph1-8" x="773.28125" y="108"/>
<use xlink:href="#glyph1-14" x="778.175781" y="108"/>
<use xlink:href="#glyph1-6" x="785.96875" y="108"/>
<use xlink:href="#glyph1-10" x="788.511719" y="108"/>
<use xlink:href="#glyph1-15" x="792.910156" y="108"/>
<use xlink:href="#glyph1-12" x="795.132812" y="108"/>
<use xlink:href="#glyph1-2" x="797.355469" y="108"/>
<use xlink:href="#glyph1-11" x="802.277344" y="108"/>
<use xlink:href="#glyph1-9" x="807.347656" y="108"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 646.222656 78 L 646.222656 124 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(80%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1167.78125 68 L 1167.78125 132 "/>
<g clip-path="url(#clip20)" clip-rule="nonzero">
<g style="fill:rgb(80%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="1169.78125" y="76"/>
<use xlink:href="#glyph1-9" x="1174.859375" y="76"/>
<use xlink:href="#glyph1-16" x="1177.996094" y="76"/>
<use xlink:href="#glyph1-15" x="1183.074219" y="76"/>
<use xlink:href="#glyph1-12" x="1185.296875" y="76"/>
<use xlink:href="#glyph1-17" x="1187.519531" y="76"/>
<use xlink:href="#glyph1-18" x="1192.597656" y="76"/>
<use xlink:href="#glyph1-19" x="1195.140625" y="76"/>
<use xlink:href="#glyph1-20" x="1200.195312" y="76"/>
<use xlink:href="#glyph1-12" x="1204.929688" y="76"/>
<use xlink:href="#glyph1-9" x="1207.152344" y="76"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 154.046875 68 L 150.046875 68 L 150.046875 128 L 154.046875 128 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1189.144531 68 L 1193.144531 68 L 1193.144531 128 L 1189.144531 128 "/>
<g clip-path="url(#clip21)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-3" x="152.046875" y="76"/>
<use xlink:href="#glyph1-21" x="155.335938" y="76"/>
<use xlink:href="#glyph1-11" x="160.40625" y="76"/>
<use xlink:href="#glyph1-22" x="165.476562" y="76"/>
<use xlink:href="#glyph1-23" x="169.476562" y="76"/>
<use xlink:href="#glyph1-2" x="173.644531" y="76"/>
<use xlink:href="#glyph1-3" x="178.566406" y="76"/>
<use xlink:href="#glyph1-4" x="181.855469" y="76"/>
<use xlink:href="#glyph1-2" x="186.589844" y="76"/>
<use xlink:href="#glyph1-3" x="191.511719" y="76"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 138.445312 46 L 138.445312 92 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 197.238281 49 L 197.238281 43 "/>
<g clip-path="url(#clip22)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-24" x="199.238281" y="44"/>
<use xlink:href="#glyph1-15" x="204.824219" y="44"/>
<use xlink:href="#glyph1-12" x="207.046875" y="44"/>
<use xlink:href="#glyph1-2" x="209.269531" y="44"/>
<use xlink:href="#glyph1-11" x="214.191406" y="44"/>
<use xlink:href="#glyph1-9" x="219.261719" y="44"/>
<use xlink:href="#glyph1-5" x="222.398438" y="44"/>
<use xlink:href="#glyph1-6" x="225.09375" y="44"/>
<use xlink:href="#glyph1-10" x="227.636719" y="44"/>
<use xlink:href="#glyph1-8" x="232.035156" y="44"/>
<use xlink:href="#glyph1-11" x="236.929688" y="44"/>
<use xlink:href="#glyph1-11" x="242" y="44"/>
<use xlink:href="#glyph1-2" x="247.070312" y="44"/>
<use xlink:href="#glyph1-10" x="251.992188" y="44"/>
<use xlink:href="#glyph1-9" x="256.390625" y="44"/>
<use xlink:href="#glyph1-12" x="259.527344" y="44"/>
<use xlink:href="#glyph1-11" x="261.75" y="44"/>
<use xlink:href="#glyph1-7" x="266.820312" y="44"/>
<use xlink:href="#glyph1-6" x="271.898438" y="44"/>
<use xlink:href="#glyph1-9" x="274.441406" y="44"/>
<use xlink:href="#glyph1-8" x="277.578125" y="44"/>
<use xlink:href="#glyph1-6" x="282.472656" y="44"/>
<use xlink:href="#glyph1-23" x="285.015625" y="44"/>
<use xlink:href="#glyph1-2" x="289.183594" y="44"/>
<use xlink:href="#glyph1-3" x="294.105469" y="44"/>
<use xlink:href="#glyph1-4" x="297.394531" y="44"/>
<use xlink:href="#glyph1-2" x="302.128906" y="44"/>
<use xlink:href="#glyph1-3" x="307.050781" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1028.417969 49 L 1028.417969 43 "/>
<g clip-path="url(#clip23)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-24" x="1030.417969" y="44"/>
<use xlink:href="#glyph1-15" x="1036.003906" y="44"/>
<use xlink:href="#glyph1-12" x="1038.226562" y="44"/>
<use xlink:href="#glyph1-2" x="1040.449219" y="44"/>
<use xlink:href="#glyph1-11" x="1045.371094" y="44"/>
<use xlink:href="#glyph1-9" x="1050.441406" y="44"/>
<use xlink:href="#glyph1-5" x="1053.578125" y="44"/>
<use xlink:href="#glyph1-6" x="1056.273438" y="44"/>
<use xlink:href="#glyph1-3" x="1058.816406" y="44"/>
<use xlink:href="#glyph1-2" x="1062.105469" y="44"/>
<use xlink:href="#glyph1-10" x="1067.027344" y="44"/>
<use xlink:href="#glyph1-2" x="1071.425781" y="44"/>
<use xlink:href="#glyph1-12" x="1076.347656" y="44"/>
<use xlink:href="#glyph1-4" x="1078.570312" y="44"/>
<use xlink:href="#glyph1-2" x="1083.304688" y="44"/>
<use xlink:href="#glyph1-16" x="1088.226562" y="44"/>
<use xlink:href="#glyph1-6" x="1093.304688" y="44"/>
<use xlink:href="#glyph1-25" x="1095.847656" y="44"/>
<use xlink:href="#glyph1-26" x="1099.527344" y="44"/>
<use xlink:href="#glyph1-2" x="1105.542969" y="44"/>
<use xlink:href="#glyph1-15" x="1110.464844" y="44"/>
<use xlink:href="#glyph1-15" x="1112.6875" y="44"/>
<use xlink:href="#glyph1-8" x="1114.910156" y="44"/>
<use xlink:href="#glyph1-6" x="1119.804688" y="44"/>
<use xlink:href="#glyph1-13" x="1122.347656" y="44"/>
<use xlink:href="#glyph1-3" x="1125.164062" y="44"/>
<use xlink:href="#glyph1-8" x="1128.453125" y="44"/>
<use xlink:href="#glyph1-14" x="1133.347656" y="44"/>
<use xlink:href="#glyph1-6" x="1141.140625" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1066.691406 36 L 1062.691406 36 L 1062.691406 64 L 1066.691406 64 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1153.382812 36 L 1157.382812 36 L 1157.382812 64 L 1153.382812 64 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-27" x="1064.691406" y="44"/>
<use xlink:href="#glyph1-3" x="1069.769531" y="44"/>
<use xlink:href="#glyph1-8" x="1073.058594" y="44"/>
<use xlink:href="#glyph1-9" x="1077.953125" y="44"/>
<use xlink:href="#glyph1-2" x="1081.089844" y="44"/>
<use xlink:href="#glyph1-10" x="1086.011719" y="44"/>
<use xlink:href="#glyph1-9" x="1090.410156" y="44"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 174.824219 36 L 170.824219 36 L 170.824219 64 L 174.824219 64 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1154.925781 36 L 1158.925781 36 L 1158.925781 64 L 1154.925781 64 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-10" x="172.824219" y="44"/>
<use xlink:href="#glyph1-15" x="177.222656" y="44"/>
<use xlink:href="#glyph1-12" x="179.445312" y="44"/>
<use xlink:href="#glyph1-2" x="181.667969" y="44"/>
<use xlink:href="#glyph1-11" x="186.589844" y="44"/>
<use xlink:href="#glyph1-9" x="191.660156" y="44"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(80%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1166.152344 36 L 1166.152344 132 "/>
<g clip-path="url(#clip24)" clip-rule="nonzero">
<g style="fill:rgb(80%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="1168.152344" y="44"/>
<use xlink:href="#glyph1-9" x="1173.230469" y="44"/>
<use xlink:href="#glyph1-16" x="1176.367188" y="44"/>
<use xlink:href="#glyph1-15" x="1181.445312" y="44"/>
<use xlink:href="#glyph1-12" x="1183.667969" y="44"/>
<use xlink:href="#glyph1-17" x="1185.890625" y="44"/>
<use xlink:href="#glyph1-18" x="1190.96875" y="44"/>
<use xlink:href="#glyph1-19" x="1193.511719" y="44"/>
<use xlink:href="#glyph1-20" x="1198.566406" y="44"/>
<use xlink:href="#glyph1-12" x="1203.300781" y="44"/>
<use xlink:href="#glyph1-9" x="1205.523438" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1235.625 36 L 1231.625 36 L 1231.625 64 L 1235.625 64 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1241.207031 36 L 1245.207031 36 L 1245.207031 64 L 1241.207031 64 "/>
<g clip-path="url(#clip25)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-27" x="1233.625" y="44"/>
<use xlink:href="#glyph1-3" x="1238.703125" y="44"/>
<use xlink:href="#glyph1-8" x="1241.992188" y="44"/>
<use xlink:href="#glyph1-9" x="1246.886719" y="44"/>
<use xlink:href="#glyph1-2" x="1250.023438" y="44"/>
<use xlink:href="#glyph1-10" x="1254.945312" y="44"/>
<use xlink:href="#glyph1-9" x="1259.34375" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 24.957031 36 L 20.957031 36 L 20.957031 128 L 24.957031 128 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1242.046875 36 L 1246.046875 36 L 1246.046875 128 L 1242.046875 128 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-14" x="22.957031" y="44"/>
<use xlink:href="#glyph1-28" x="30.75" y="44"/>
<use xlink:href="#glyph1-12" x="35.652344" y="44"/>
<use xlink:href="#glyph1-11" x="37.875" y="44"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 5.941406 46 L 5.941406 60 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 84 KiB

BIN
doc/traces/switch-mock.fxt Normal file

Binary file not shown.

530
doc/traces/switch-mock.svg Normal file
View File

@ -0,0 +1,530 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1280pt" height="184pt" viewBox="0 0 1280 184" version="1.1">
<defs>
<g>
<symbol overflow="visible" id="glyph0-0">
<path style="stroke:none;" d="M 0.59375 2.125 L 0.59375 -8.46875 L 6.59375 -8.46875 L 6.59375 2.125 Z M 1.265625 1.453125 L 5.9375 1.453125 L 5.9375 -7.78125 L 1.265625 -7.78125 Z M 1.265625 1.453125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-1">
<path style="stroke:none;" d="M 1.171875 -8.75 L 6.703125 -8.75 L 6.703125 -7.75 L 2.359375 -7.75 L 2.359375 -5.15625 L 6.53125 -5.15625 L 6.53125 -4.171875 L 2.359375 -4.171875 L 2.359375 -1 L 6.8125 -1 L 6.8125 0 L 1.171875 0 Z M 1.171875 -8.75 "/>
</symbol>
<symbol overflow="visible" id="glyph0-2">
<path style="stroke:none;" d="M 4.109375 -3.296875 C 3.242188 -3.296875 2.640625 -3.195312 2.296875 -3 C 1.960938 -2.800781 1.796875 -2.460938 1.796875 -1.984375 C 1.796875 -1.597656 1.921875 -1.289062 2.171875 -1.0625 C 2.429688 -0.84375 2.773438 -0.734375 3.203125 -0.734375 C 3.804688 -0.734375 4.285156 -0.941406 4.640625 -1.359375 C 5.003906 -1.785156 5.1875 -2.351562 5.1875 -3.0625 L 5.1875 -3.296875 Z M 6.265625 -3.75 L 6.265625 0 L 5.1875 0 L 5.1875 -1 C 4.9375 -0.601562 4.628906 -0.304688 4.265625 -0.109375 C 3.898438 0.078125 3.453125 0.171875 2.921875 0.171875 C 2.242188 0.171875 1.707031 -0.015625 1.3125 -0.390625 C 0.914062 -0.773438 0.71875 -1.28125 0.71875 -1.90625 C 0.71875 -2.644531 0.960938 -3.203125 1.453125 -3.578125 C 1.953125 -3.953125 2.691406 -4.140625 3.671875 -4.140625 L 5.1875 -4.140625 L 5.1875 -4.25 C 5.1875 -4.75 5.019531 -5.132812 4.6875 -5.40625 C 4.363281 -5.675781 3.910156 -5.8125 3.328125 -5.8125 C 2.953125 -5.8125 2.582031 -5.765625 2.21875 -5.671875 C 1.863281 -5.578125 1.523438 -5.441406 1.203125 -5.265625 L 1.203125 -6.265625 C 1.597656 -6.421875 1.976562 -6.535156 2.34375 -6.609375 C 2.71875 -6.679688 3.082031 -6.71875 3.4375 -6.71875 C 4.382812 -6.71875 5.09375 -6.472656 5.5625 -5.984375 C 6.03125 -5.492188 6.265625 -4.75 6.265625 -3.75 Z M 6.265625 -3.75 "/>
</symbol>
<symbol overflow="visible" id="glyph0-3">
<path style="stroke:none;" d="M 5.859375 -6.3125 L 5.859375 -5.296875 C 5.546875 -5.472656 5.238281 -5.601562 4.9375 -5.6875 C 4.632812 -5.769531 4.328125 -5.8125 4.015625 -5.8125 C 3.304688 -5.8125 2.757812 -5.585938 2.375 -5.140625 C 1.988281 -4.703125 1.796875 -4.082031 1.796875 -3.28125 C 1.796875 -2.476562 1.988281 -1.851562 2.375 -1.40625 C 2.757812 -0.96875 3.304688 -0.75 4.015625 -0.75 C 4.328125 -0.75 4.632812 -0.789062 4.9375 -0.875 C 5.238281 -0.957031 5.546875 -1.082031 5.859375 -1.25 L 5.859375 -0.25 C 5.554688 -0.113281 5.242188 -0.0078125 4.921875 0.0625 C 4.597656 0.132812 4.253906 0.171875 3.890625 0.171875 C 2.898438 0.171875 2.113281 -0.132812 1.53125 -0.75 C 0.945312 -1.375 0.65625 -2.21875 0.65625 -3.28125 C 0.65625 -4.34375 0.945312 -5.179688 1.53125 -5.796875 C 2.125 -6.410156 2.9375 -6.71875 3.96875 -6.71875 C 4.289062 -6.71875 4.609375 -6.679688 4.921875 -6.609375 C 5.242188 -6.546875 5.554688 -6.445312 5.859375 -6.3125 Z M 5.859375 -6.3125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-4">
<path style="stroke:none;" d="M 6.59375 -3.96875 L 6.59375 0 L 5.515625 0 L 5.515625 -3.921875 C 5.515625 -4.546875 5.390625 -5.007812 5.140625 -5.3125 C 4.898438 -5.625 4.539062 -5.78125 4.0625 -5.78125 C 3.476562 -5.78125 3.015625 -5.59375 2.671875 -5.21875 C 2.335938 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -9.125 L 2.171875 -9.125 L 2.171875 -5.546875 C 2.429688 -5.941406 2.734375 -6.234375 3.078125 -6.421875 C 3.429688 -6.617188 3.835938 -6.71875 4.296875 -6.71875 C 5.046875 -6.71875 5.613281 -6.484375 6 -6.015625 C 6.394531 -5.554688 6.59375 -4.875 6.59375 -3.96875 Z M 6.59375 -3.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-5">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph0-6">
<path style="stroke:none;" d="M 5.453125 -3.359375 C 5.453125 -4.140625 5.289062 -4.742188 4.96875 -5.171875 C 4.644531 -5.597656 4.191406 -5.8125 3.609375 -5.8125 C 3.035156 -5.8125 2.585938 -5.597656 2.265625 -5.171875 C 1.941406 -4.742188 1.78125 -4.140625 1.78125 -3.359375 C 1.78125 -2.578125 1.941406 -1.972656 2.265625 -1.546875 C 2.585938 -1.117188 3.035156 -0.90625 3.609375 -0.90625 C 4.191406 -0.90625 4.644531 -1.117188 4.96875 -1.546875 C 5.289062 -1.972656 5.453125 -2.578125 5.453125 -3.359375 Z M 6.53125 -0.8125 C 6.53125 0.300781 6.28125 1.128906 5.78125 1.671875 C 5.289062 2.222656 4.53125 2.5 3.5 2.5 C 3.125 2.5 2.765625 2.46875 2.421875 2.40625 C 2.085938 2.351562 1.765625 2.269531 1.453125 2.15625 L 1.453125 1.109375 C 1.765625 1.273438 2.078125 1.398438 2.390625 1.484375 C 2.703125 1.566406 3.015625 1.609375 3.328125 1.609375 C 4.035156 1.609375 4.566406 1.421875 4.921875 1.046875 C 5.273438 0.679688 5.453125 0.125 5.453125 -0.625 L 5.453125 -1.15625 C 5.222656 -0.769531 4.9375 -0.476562 4.59375 -0.28125 C 4.25 -0.09375 3.832031 0 3.34375 0 C 2.539062 0 1.890625 -0.304688 1.390625 -0.921875 C 0.898438 -1.535156 0.65625 -2.347656 0.65625 -3.359375 C 0.65625 -4.367188 0.898438 -5.179688 1.390625 -5.796875 C 1.890625 -6.410156 2.539062 -6.71875 3.34375 -6.71875 C 3.832031 -6.71875 4.25 -6.617188 4.59375 -6.421875 C 4.9375 -6.234375 5.222656 -5.945312 5.453125 -5.5625 L 5.453125 -6.5625 L 6.53125 -6.5625 Z M 6.53125 -0.8125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-7">
<path style="stroke:none;" d="M 4.9375 -5.5625 C 4.8125 -5.625 4.675781 -5.671875 4.53125 -5.703125 C 4.394531 -5.742188 4.238281 -5.765625 4.0625 -5.765625 C 3.457031 -5.765625 2.988281 -5.566406 2.65625 -5.171875 C 2.332031 -4.773438 2.171875 -4.203125 2.171875 -3.453125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.398438 -5.941406 2.695312 -6.234375 3.0625 -6.421875 C 3.425781 -6.617188 3.867188 -6.71875 4.390625 -6.71875 C 4.460938 -6.71875 4.539062 -6.710938 4.625 -6.703125 C 4.71875 -6.691406 4.816406 -6.675781 4.921875 -6.65625 Z M 4.9375 -5.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-8">
<path style="stroke:none;" d="M 1.125 -6.5625 L 2.203125 -6.5625 L 2.203125 0 L 1.125 0 Z M 1.125 -9.125 L 2.203125 -9.125 L 2.203125 -7.75 L 1.125 -7.75 Z M 1.125 -9.125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-9">
<path style="stroke:none;" d="M 5.453125 -5.5625 L 5.453125 -9.125 L 6.53125 -9.125 L 6.53125 0 L 5.453125 0 L 5.453125 -0.984375 C 5.222656 -0.597656 4.9375 -0.304688 4.59375 -0.109375 C 4.25 0.078125 3.832031 0.171875 3.34375 0.171875 C 2.550781 0.171875 1.90625 -0.144531 1.40625 -0.78125 C 0.90625 -1.414062 0.65625 -2.25 0.65625 -3.28125 C 0.65625 -4.3125 0.90625 -5.140625 1.40625 -5.765625 C 1.90625 -6.398438 2.550781 -6.71875 3.34375 -6.71875 C 3.832031 -6.71875 4.25 -6.625 4.59375 -6.4375 C 4.9375 -6.25 5.222656 -5.957031 5.453125 -5.5625 Z M 1.78125 -3.28125 C 1.78125 -2.488281 1.941406 -1.863281 2.265625 -1.40625 C 2.585938 -0.957031 3.035156 -0.734375 3.609375 -0.734375 C 4.179688 -0.734375 4.628906 -0.957031 4.953125 -1.40625 C 5.285156 -1.863281 5.453125 -2.488281 5.453125 -3.28125 C 5.453125 -4.070312 5.285156 -4.691406 4.953125 -5.140625 C 4.628906 -5.585938 4.179688 -5.8125 3.609375 -5.8125 C 3.035156 -5.8125 2.585938 -5.585938 2.265625 -5.140625 C 1.941406 -4.691406 1.78125 -4.070312 1.78125 -3.28125 Z M 1.78125 -3.28125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-10">
<path style="stroke:none;" d="M 0.359375 -6.5625 L 1.5 -6.5625 L 3.546875 -1.0625 L 5.609375 -6.5625 L 6.75 -6.5625 L 4.28125 0 L 2.8125 0 Z M 0.359375 -6.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-11">
<path style="stroke:none;" d="M 5.3125 -6.375 L 5.3125 -5.34375 C 5.007812 -5.5 4.691406 -5.613281 4.359375 -5.6875 C 4.035156 -5.769531 3.695312 -5.8125 3.34375 -5.8125 C 2.8125 -5.8125 2.410156 -5.726562 2.140625 -5.5625 C 1.867188 -5.40625 1.734375 -5.160156 1.734375 -4.828125 C 1.734375 -4.578125 1.828125 -4.378906 2.015625 -4.234375 C 2.210938 -4.097656 2.601562 -3.96875 3.1875 -3.84375 L 3.546875 -3.75 C 4.316406 -3.59375 4.863281 -3.363281 5.1875 -3.0625 C 5.507812 -2.757812 5.671875 -2.34375 5.671875 -1.8125 C 5.671875 -1.195312 5.425781 -0.710938 4.9375 -0.359375 C 4.457031 -0.00390625 3.796875 0.171875 2.953125 0.171875 C 2.597656 0.171875 2.226562 0.132812 1.84375 0.0625 C 1.46875 0 1.070312 -0.0976562 0.65625 -0.234375 L 0.65625 -1.359375 C 1.050781 -1.148438 1.441406 -0.992188 1.828125 -0.890625 C 2.210938 -0.785156 2.597656 -0.734375 2.984375 -0.734375 C 3.484375 -0.734375 3.867188 -0.816406 4.140625 -0.984375 C 4.421875 -1.160156 4.5625 -1.410156 4.5625 -1.734375 C 4.5625 -2.023438 4.460938 -2.25 4.265625 -2.40625 C 4.066406 -2.5625 3.632812 -2.710938 2.96875 -2.859375 L 2.59375 -2.9375 C 1.925781 -3.082031 1.441406 -3.300781 1.140625 -3.59375 C 0.847656 -3.882812 0.703125 -4.28125 0.703125 -4.78125 C 0.703125 -5.40625 0.921875 -5.882812 1.359375 -6.21875 C 1.796875 -6.550781 2.414062 -6.71875 3.21875 -6.71875 C 3.613281 -6.71875 3.988281 -6.6875 4.34375 -6.625 C 4.695312 -6.570312 5.019531 -6.488281 5.3125 -6.375 Z M 5.3125 -6.375 "/>
</symbol>
<symbol overflow="visible" id="glyph0-12">
<path style="stroke:none;" d="M 3.671875 -5.8125 C 3.097656 -5.8125 2.640625 -5.582031 2.296875 -5.125 C 1.960938 -4.675781 1.796875 -4.0625 1.796875 -3.28125 C 1.796875 -2.488281 1.960938 -1.867188 2.296875 -1.421875 C 2.628906 -0.972656 3.085938 -0.75 3.671875 -0.75 C 4.242188 -0.75 4.695312 -0.972656 5.03125 -1.421875 C 5.375 -1.878906 5.546875 -2.5 5.546875 -3.28125 C 5.546875 -4.050781 5.375 -4.664062 5.03125 -5.125 C 4.695312 -5.582031 4.242188 -5.8125 3.671875 -5.8125 Z M 3.671875 -6.71875 C 4.609375 -6.71875 5.34375 -6.410156 5.875 -5.796875 C 6.414062 -5.191406 6.6875 -4.351562 6.6875 -3.28125 C 6.6875 -2.207031 6.414062 -1.363281 5.875 -0.75 C 5.34375 -0.132812 4.609375 0.171875 3.671875 0.171875 C 2.734375 0.171875 1.992188 -0.132812 1.453125 -0.75 C 0.921875 -1.363281 0.65625 -2.207031 0.65625 -3.28125 C 0.65625 -4.351562 0.921875 -5.191406 1.453125 -5.796875 C 1.992188 -6.410156 2.734375 -6.71875 3.671875 -6.71875 Z M 3.671875 -6.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-13">
<path style="stroke:none;" d="M 6.59375 -3.96875 L 6.59375 0 L 5.515625 0 L 5.515625 -3.921875 C 5.515625 -4.546875 5.390625 -5.007812 5.140625 -5.3125 C 4.898438 -5.625 4.539062 -5.78125 4.0625 -5.78125 C 3.476562 -5.78125 3.015625 -5.59375 2.671875 -5.21875 C 2.335938 -4.851562 2.171875 -4.347656 2.171875 -3.703125 L 2.171875 0 L 1.09375 0 L 1.09375 -6.5625 L 2.171875 -6.5625 L 2.171875 -5.546875 C 2.429688 -5.941406 2.734375 -6.234375 3.078125 -6.421875 C 3.429688 -6.617188 3.835938 -6.71875 4.296875 -6.71875 C 5.046875 -6.71875 5.613281 -6.484375 6 -6.015625 C 6.394531 -5.554688 6.59375 -4.875 6.59375 -3.96875 Z M 6.59375 -3.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-14">
<path style="stroke:none;" d="M 1.40625 -1.484375 L 2.640625 -1.484375 L 2.640625 0 L 1.40625 0 Z M 1.40625 -6.203125 L 2.640625 -6.203125 L 2.640625 -4.71875 L 1.40625 -4.71875 Z M 1.40625 -6.203125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-15">
<path style="stroke:none;" d="M 1.484375 -1 L 3.421875 -1 L 3.421875 -7.671875 L 1.3125 -7.25 L 1.3125 -8.328125 L 3.40625 -8.75 L 4.59375 -8.75 L 4.59375 -1 L 6.53125 -1 L 6.53125 0 L 1.484375 0 Z M 1.484375 -1 "/>
</symbol>
<symbol overflow="visible" id="glyph0-16">
<path style="stroke:none;" d="M 3.8125 -7.96875 C 3.207031 -7.96875 2.75 -7.664062 2.4375 -7.0625 C 2.132812 -6.46875 1.984375 -5.566406 1.984375 -4.359375 C 1.984375 -3.160156 2.132812 -2.257812 2.4375 -1.65625 C 2.75 -1.0625 3.207031 -0.765625 3.8125 -0.765625 C 4.425781 -0.765625 4.882812 -1.0625 5.1875 -1.65625 C 5.5 -2.257812 5.65625 -3.160156 5.65625 -4.359375 C 5.65625 -5.566406 5.5 -6.46875 5.1875 -7.0625 C 4.882812 -7.664062 4.425781 -7.96875 3.8125 -7.96875 Z M 3.8125 -8.90625 C 4.789062 -8.90625 5.539062 -8.515625 6.0625 -7.734375 C 6.582031 -6.960938 6.84375 -5.835938 6.84375 -4.359375 C 6.84375 -2.890625 6.582031 -1.765625 6.0625 -0.984375 C 5.539062 -0.210938 4.789062 0.171875 3.8125 0.171875 C 2.832031 0.171875 2.082031 -0.210938 1.5625 -0.984375 C 1.050781 -1.765625 0.796875 -2.890625 0.796875 -4.359375 C 0.796875 -5.835938 1.050781 -6.960938 1.5625 -7.734375 C 2.082031 -8.515625 2.832031 -8.90625 3.8125 -8.90625 Z M 3.8125 -8.90625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-17">
<path style="stroke:none;" d="M 1.015625 -2.59375 L 1.015625 -6.5625 L 2.09375 -6.5625 L 2.09375 -2.625 C 2.09375 -2.007812 2.210938 -1.546875 2.453125 -1.234375 C 2.703125 -0.921875 3.066406 -0.765625 3.546875 -0.765625 C 4.128906 -0.765625 4.585938 -0.945312 4.921875 -1.3125 C 5.265625 -1.6875 5.4375 -2.195312 5.4375 -2.84375 L 5.4375 -6.5625 L 6.515625 -6.5625 L 6.515625 0 L 5.4375 0 L 5.4375 -1.015625 C 5.175781 -0.609375 4.875 -0.304688 4.53125 -0.109375 C 4.1875 0.078125 3.785156 0.171875 3.328125 0.171875 C 2.566406 0.171875 1.988281 -0.0625 1.59375 -0.53125 C 1.207031 -1 1.015625 -1.6875 1.015625 -2.59375 Z M 3.734375 -6.71875 Z M 3.734375 -6.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-18">
<path style="stroke:none;" d="M 2.203125 -8.421875 L 2.203125 -6.5625 L 4.421875 -6.5625 L 4.421875 -5.71875 L 2.203125 -5.71875 L 2.203125 -2.15625 C 2.203125 -1.625 2.273438 -1.28125 2.421875 -1.125 C 2.566406 -0.976562 2.863281 -0.90625 3.3125 -0.90625 L 4.421875 -0.90625 L 4.421875 0 L 3.3125 0 C 2.476562 0 1.898438 -0.15625 1.578125 -0.46875 C 1.265625 -0.78125 1.109375 -1.34375 1.109375 -2.15625 L 1.109375 -5.71875 L 0.328125 -5.71875 L 0.328125 -6.5625 L 1.109375 -6.5625 L 1.109375 -8.421875 Z M 2.203125 -8.421875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-19">
<path style="stroke:none;" d="M 6.75 -3.546875 L 6.75 -3.03125 L 1.78125 -3.03125 C 1.832031 -2.28125 2.054688 -1.710938 2.453125 -1.328125 C 2.859375 -0.941406 3.414062 -0.75 4.125 -0.75 C 4.539062 -0.75 4.941406 -0.796875 5.328125 -0.890625 C 5.722656 -0.992188 6.113281 -1.148438 6.5 -1.359375 L 6.5 -0.328125 C 6.101562 -0.171875 5.703125 -0.0507812 5.296875 0.03125 C 4.890625 0.125 4.476562 0.171875 4.0625 0.171875 C 3.019531 0.171875 2.191406 -0.128906 1.578125 -0.734375 C 0.960938 -1.347656 0.65625 -2.175781 0.65625 -3.21875 C 0.65625 -4.289062 0.945312 -5.140625 1.53125 -5.765625 C 2.113281 -6.398438 2.894531 -6.71875 3.875 -6.71875 C 4.757812 -6.71875 5.457031 -6.429688 5.96875 -5.859375 C 6.488281 -5.296875 6.75 -4.523438 6.75 -3.546875 Z M 5.671875 -3.875 C 5.660156 -4.457031 5.492188 -4.925781 5.171875 -5.28125 C 4.847656 -5.632812 4.421875 -5.8125 3.890625 -5.8125 C 3.285156 -5.8125 2.800781 -5.640625 2.4375 -5.296875 C 2.082031 -4.953125 1.878906 -4.472656 1.828125 -3.859375 Z M 5.671875 -3.875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-20">
<path style="stroke:none;" d="M 1.125 -9.125 L 2.203125 -9.125 L 2.203125 0 L 1.125 0 Z M 1.125 -9.125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-21">
<path style="stroke:none;" d="M 6.421875 -8.46875 L 6.421875 -7.3125 C 5.972656 -7.519531 5.546875 -7.675781 5.140625 -7.78125 C 4.742188 -7.894531 4.363281 -7.953125 4 -7.953125 C 3.351562 -7.953125 2.851562 -7.828125 2.5 -7.578125 C 2.15625 -7.328125 1.984375 -6.96875 1.984375 -6.5 C 1.984375 -6.113281 2.097656 -5.820312 2.328125 -5.625 C 2.554688 -5.425781 3 -5.269531 3.65625 -5.15625 L 4.359375 -5 C 5.242188 -4.832031 5.894531 -4.535156 6.3125 -4.109375 C 6.738281 -3.691406 6.953125 -3.128906 6.953125 -2.421875 C 6.953125 -1.566406 6.664062 -0.921875 6.09375 -0.484375 C 5.53125 -0.046875 4.695312 0.171875 3.59375 0.171875 C 3.1875 0.171875 2.75 0.125 2.28125 0.03125 C 1.8125 -0.0625 1.328125 -0.203125 0.828125 -0.390625 L 0.828125 -1.609375 C 1.304688 -1.335938 1.773438 -1.132812 2.234375 -1 C 2.703125 -0.863281 3.15625 -0.796875 3.59375 -0.796875 C 4.269531 -0.796875 4.789062 -0.925781 5.15625 -1.1875 C 5.53125 -1.457031 5.71875 -1.835938 5.71875 -2.328125 C 5.71875 -2.753906 5.582031 -3.085938 5.3125 -3.328125 C 5.050781 -3.578125 4.617188 -3.757812 4.015625 -3.875 L 3.296875 -4.015625 C 2.410156 -4.191406 1.769531 -4.46875 1.375 -4.84375 C 0.988281 -5.21875 0.796875 -5.738281 0.796875 -6.40625 C 0.796875 -7.1875 1.066406 -7.796875 1.609375 -8.234375 C 2.148438 -8.679688 2.898438 -8.90625 3.859375 -8.90625 C 4.273438 -8.90625 4.691406 -8.867188 5.109375 -8.796875 C 5.535156 -8.722656 5.972656 -8.613281 6.421875 -8.46875 Z M 6.421875 -8.46875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-22">
<path style="stroke:none;" d="M 0.5 -6.5625 L 1.578125 -6.5625 L 2.9375 -1.4375 L 4.265625 -6.5625 L 5.546875 -6.5625 L 6.890625 -1.4375 L 8.234375 -6.5625 L 9.3125 -6.5625 L 7.59375 0 L 6.328125 0 L 4.90625 -5.375 L 3.5 0 L 2.21875 0 Z M 0.5 -6.5625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-23">
<path style="stroke:none;" d="M 1.28125 -1.484375 L 2.515625 -1.484375 L 2.515625 0 L 1.28125 0 Z M 1.28125 -1.484375 "/>
</symbol>
<symbol overflow="visible" id="glyph0-24">
<path style="stroke:none;" d="M 6.125 2 L 6.125 2.828125 L -0.125 2.828125 L -0.125 2 Z M 6.125 2 "/>
</symbol>
<symbol overflow="visible" id="glyph1-0">
<path style="stroke:none;" d="M 0.40625 1.421875 L 0.40625 -5.640625 L 4.40625 -5.640625 L 4.40625 1.421875 Z M 0.84375 0.96875 L 3.953125 0.96875 L 3.953125 -5.1875 L 0.84375 -5.1875 Z M 0.84375 0.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-1">
<path style="stroke:none;" d="M 0.78125 -5.828125 L 1.578125 -5.828125 L 1.578125 -0.40625 C 1.578125 0.300781 1.441406 0.8125 1.171875 1.125 C 0.910156 1.445312 0.484375 1.609375 -0.109375 1.609375 L -0.421875 1.609375 L -0.421875 0.9375 L -0.171875 0.9375 C 0.179688 0.9375 0.425781 0.835938 0.5625 0.640625 C 0.707031 0.453125 0.78125 0.101562 0.78125 -0.40625 Z M 0.78125 -5.828125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-2">
<path style="stroke:none;" d="M 2.453125 -3.875 C 2.066406 -3.875 1.757812 -3.722656 1.53125 -3.421875 C 1.3125 -3.117188 1.203125 -2.707031 1.203125 -2.1875 C 1.203125 -1.664062 1.3125 -1.253906 1.53125 -0.953125 C 1.757812 -0.648438 2.066406 -0.5 2.453125 -0.5 C 2.835938 -0.5 3.140625 -0.648438 3.359375 -0.953125 C 3.585938 -1.253906 3.703125 -1.664062 3.703125 -2.1875 C 3.703125 -2.707031 3.585938 -3.117188 3.359375 -3.421875 C 3.140625 -3.722656 2.835938 -3.875 2.453125 -3.875 Z M 2.453125 -4.484375 C 3.078125 -4.484375 3.566406 -4.28125 3.921875 -3.875 C 4.273438 -3.46875 4.453125 -2.90625 4.453125 -2.1875 C 4.453125 -1.46875 4.273438 -0.90625 3.921875 -0.5 C 3.566406 -0.09375 3.078125 0.109375 2.453125 0.109375 C 1.828125 0.109375 1.332031 -0.09375 0.96875 -0.5 C 0.613281 -0.90625 0.4375 -1.46875 0.4375 -2.1875 C 0.4375 -2.90625 0.613281 -3.46875 0.96875 -3.875 C 1.332031 -4.28125 1.828125 -4.484375 2.453125 -4.484375 Z M 2.453125 -4.484375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-3">
<path style="stroke:none;" d="M 3.890625 -2.1875 C 3.890625 -2.71875 3.78125 -3.128906 3.5625 -3.421875 C 3.351562 -3.722656 3.054688 -3.875 2.671875 -3.875 C 2.296875 -3.875 2 -3.722656 1.78125 -3.421875 C 1.5625 -3.128906 1.453125 -2.71875 1.453125 -2.1875 C 1.453125 -1.65625 1.5625 -1.238281 1.78125 -0.9375 C 2 -0.632812 2.296875 -0.484375 2.671875 -0.484375 C 3.054688 -0.484375 3.351562 -0.632812 3.5625 -0.9375 C 3.78125 -1.238281 3.890625 -1.65625 3.890625 -2.1875 Z M 1.453125 -3.71875 C 1.597656 -3.976562 1.785156 -4.171875 2.015625 -4.296875 C 2.253906 -4.421875 2.53125 -4.484375 2.84375 -4.484375 C 3.375 -4.484375 3.804688 -4.269531 4.140625 -3.84375 C 4.472656 -3.425781 4.640625 -2.875 4.640625 -2.1875 C 4.640625 -1.5 4.472656 -0.941406 4.140625 -0.515625 C 3.804688 -0.0976562 3.375 0.109375 2.84375 0.109375 C 2.53125 0.109375 2.253906 0.046875 2.015625 -0.078125 C 1.785156 -0.203125 1.597656 -0.394531 1.453125 -0.65625 L 1.453125 0 L 0.734375 0 L 0.734375 -6.078125 L 1.453125 -6.078125 Z M 1.453125 -3.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-4">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph1-5">
<path style="stroke:none;" d="M 1 -0.671875 L 2.28125 -0.671875 L 2.28125 -5.109375 L 0.875 -4.828125 L 0.875 -5.546875 L 2.28125 -5.828125 L 3.0625 -5.828125 L 3.0625 -0.671875 L 4.359375 -0.671875 L 4.359375 0 L 1 0 Z M 1 -0.671875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-6">
<path style="stroke:none;" d="M 3.546875 -4.25 L 3.546875 -3.5625 C 3.335938 -3.664062 3.125 -3.742188 2.90625 -3.796875 C 2.6875 -3.847656 2.460938 -3.875 2.234375 -3.875 C 1.878906 -3.875 1.609375 -3.816406 1.421875 -3.703125 C 1.242188 -3.597656 1.15625 -3.4375 1.15625 -3.21875 C 1.15625 -3.050781 1.21875 -2.921875 1.34375 -2.828125 C 1.476562 -2.734375 1.738281 -2.644531 2.125 -2.5625 L 2.375 -2.5 C 2.882812 -2.394531 3.242188 -2.242188 3.453125 -2.046875 C 3.671875 -1.847656 3.78125 -1.566406 3.78125 -1.203125 C 3.78125 -0.796875 3.617188 -0.472656 3.296875 -0.234375 C 2.972656 -0.00390625 2.53125 0.109375 1.96875 0.109375 C 1.738281 0.109375 1.492188 0.0820312 1.234375 0.03125 C 0.984375 -0.0078125 0.71875 -0.0703125 0.4375 -0.15625 L 0.4375 -0.90625 C 0.695312 -0.769531 0.957031 -0.664062 1.21875 -0.59375 C 1.476562 -0.519531 1.734375 -0.484375 1.984375 -0.484375 C 2.328125 -0.484375 2.585938 -0.539062 2.765625 -0.65625 C 2.953125 -0.78125 3.046875 -0.945312 3.046875 -1.15625 C 3.046875 -1.351562 2.976562 -1.503906 2.84375 -1.609375 C 2.707031 -1.710938 2.421875 -1.8125 1.984375 -1.90625 L 1.734375 -1.96875 C 1.285156 -2.0625 0.960938 -2.203125 0.765625 -2.390625 C 0.566406 -2.585938 0.46875 -2.851562 0.46875 -3.1875 C 0.46875 -3.601562 0.613281 -3.921875 0.90625 -4.140625 C 1.195312 -4.367188 1.609375 -4.484375 2.140625 -4.484375 C 2.410156 -4.484375 2.660156 -4.460938 2.890625 -4.421875 C 3.128906 -4.378906 3.347656 -4.320312 3.546875 -4.25 Z M 3.546875 -4.25 "/>
</symbol>
<symbol overflow="visible" id="glyph1-7">
<path style="stroke:none;" d="M 1.46875 -5.625 L 1.46875 -4.375 L 2.953125 -4.375 L 2.953125 -3.8125 L 1.46875 -3.8125 L 1.46875 -1.4375 C 1.46875 -1.082031 1.515625 -0.851562 1.609375 -0.75 C 1.710938 -0.65625 1.910156 -0.609375 2.203125 -0.609375 L 2.953125 -0.609375 L 2.953125 0 L 2.203125 0 C 1.648438 0 1.269531 -0.101562 1.0625 -0.3125 C 0.851562 -0.519531 0.75 -0.894531 0.75 -1.4375 L 0.75 -3.8125 L 0.21875 -3.8125 L 0.21875 -4.375 L 0.75 -4.375 L 0.75 -5.625 Z M 1.46875 -5.625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-8">
<path style="stroke:none;" d="M 2.75 -2.203125 C 2.164062 -2.203125 1.757812 -2.132812 1.53125 -2 C 1.3125 -1.863281 1.203125 -1.640625 1.203125 -1.328125 C 1.203125 -1.066406 1.285156 -0.859375 1.453125 -0.703125 C 1.617188 -0.554688 1.847656 -0.484375 2.140625 -0.484375 C 2.535156 -0.484375 2.851562 -0.625 3.09375 -0.90625 C 3.332031 -1.195312 3.453125 -1.578125 3.453125 -2.046875 L 3.453125 -2.203125 Z M 4.171875 -2.5 L 4.171875 0 L 3.453125 0 L 3.453125 -0.671875 C 3.296875 -0.398438 3.09375 -0.203125 2.84375 -0.078125 C 2.601562 0.046875 2.304688 0.109375 1.953125 0.109375 C 1.503906 0.109375 1.144531 -0.015625 0.875 -0.265625 C 0.613281 -0.515625 0.484375 -0.851562 0.484375 -1.28125 C 0.484375 -1.769531 0.644531 -2.140625 0.96875 -2.390625 C 1.300781 -2.640625 1.796875 -2.765625 2.453125 -2.765625 L 3.453125 -2.765625 L 3.453125 -2.828125 C 3.453125 -3.160156 3.34375 -3.414062 3.125 -3.59375 C 2.914062 -3.78125 2.613281 -3.875 2.21875 -3.875 C 1.96875 -3.875 1.722656 -3.84375 1.484375 -3.78125 C 1.242188 -3.71875 1.015625 -3.628906 0.796875 -3.515625 L 0.796875 -4.171875 C 1.066406 -4.273438 1.320312 -4.351562 1.5625 -4.40625 C 1.8125 -4.457031 2.054688 -4.484375 2.296875 -4.484375 C 2.921875 -4.484375 3.390625 -4.316406 3.703125 -3.984375 C 4.015625 -3.660156 4.171875 -3.164062 4.171875 -2.5 Z M 4.171875 -2.5 "/>
</symbol>
<symbol overflow="visible" id="glyph1-9">
<path style="stroke:none;" d="M 3.296875 -3.703125 C 3.210938 -3.753906 3.125 -3.789062 3.03125 -3.8125 C 2.9375 -3.832031 2.832031 -3.84375 2.71875 -3.84375 C 2.3125 -3.84375 2 -3.707031 1.78125 -3.4375 C 1.5625 -3.175781 1.453125 -2.800781 1.453125 -2.3125 L 1.453125 0 L 0.734375 0 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.703125 C 1.597656 -3.960938 1.789062 -4.15625 2.03125 -4.28125 C 2.28125 -4.414062 2.578125 -4.484375 2.921875 -4.484375 C 2.972656 -4.484375 3.023438 -4.476562 3.078125 -4.46875 C 3.140625 -4.46875 3.207031 -4.457031 3.28125 -4.4375 Z M 3.296875 -3.703125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-10">
<path style="stroke:none;" d="M 0.75 -4.375 L 1.46875 -4.375 L 1.46875 0 L 0.75 0 Z M 0.75 -6.078125 L 1.46875 -6.078125 L 1.46875 -5.171875 L 0.75 -5.171875 Z M 0.75 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-11">
<path style="stroke:none;" d="M 4.390625 -2.640625 L 4.390625 0 L 3.671875 0 L 3.671875 -2.625 C 3.671875 -3.03125 3.585938 -3.335938 3.421875 -3.546875 C 3.265625 -3.753906 3.023438 -3.859375 2.703125 -3.859375 C 2.316406 -3.859375 2.007812 -3.734375 1.78125 -3.484375 C 1.5625 -3.234375 1.453125 -2.894531 1.453125 -2.46875 L 1.453125 0 L 0.734375 0 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.703125 C 1.617188 -3.960938 1.816406 -4.15625 2.046875 -4.28125 C 2.285156 -4.414062 2.554688 -4.484375 2.859375 -4.484375 C 3.367188 -4.484375 3.75 -4.328125 4 -4.015625 C 4.257812 -3.703125 4.390625 -3.242188 4.390625 -2.640625 Z M 4.390625 -2.640625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-12">
<path style="stroke:none;" d="M 3.640625 -2.234375 C 3.640625 -2.753906 3.53125 -3.15625 3.3125 -3.4375 C 3.09375 -3.726562 2.789062 -3.875 2.40625 -3.875 C 2.019531 -3.875 1.71875 -3.726562 1.5 -3.4375 C 1.289062 -3.15625 1.1875 -2.753906 1.1875 -2.234375 C 1.1875 -1.722656 1.289062 -1.320312 1.5 -1.03125 C 1.71875 -0.75 2.019531 -0.609375 2.40625 -0.609375 C 2.789062 -0.609375 3.09375 -0.75 3.3125 -1.03125 C 3.53125 -1.320312 3.640625 -1.722656 3.640625 -2.234375 Z M 4.359375 -0.546875 C 4.359375 0.203125 4.191406 0.757812 3.859375 1.125 C 3.523438 1.488281 3.019531 1.671875 2.34375 1.671875 C 2.082031 1.671875 1.835938 1.648438 1.609375 1.609375 C 1.390625 1.566406 1.175781 1.507812 0.96875 1.4375 L 0.96875 0.734375 C 1.175781 0.847656 1.382812 0.929688 1.59375 0.984375 C 1.800781 1.046875 2.007812 1.078125 2.21875 1.078125 C 2.695312 1.078125 3.050781 0.953125 3.28125 0.703125 C 3.519531 0.453125 3.640625 0.078125 3.640625 -0.421875 L 3.640625 -0.765625 C 3.484375 -0.515625 3.289062 -0.320312 3.0625 -0.1875 C 2.832031 -0.0625 2.554688 0 2.234375 0 C 1.691406 0 1.253906 -0.203125 0.921875 -0.609375 C 0.597656 -1.023438 0.4375 -1.566406 0.4375 -2.234375 C 0.4375 -2.910156 0.597656 -3.453125 0.921875 -3.859375 C 1.253906 -4.273438 1.691406 -4.484375 2.234375 -4.484375 C 2.554688 -4.484375 2.832031 -4.414062 3.0625 -4.28125 C 3.289062 -4.15625 3.484375 -3.96875 3.640625 -3.71875 L 3.640625 -4.375 L 4.359375 -4.375 Z M 4.359375 -0.546875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-13">
<path style="stroke:none;" d="M 3.640625 -3.71875 L 3.640625 -6.078125 L 4.359375 -6.078125 L 4.359375 0 L 3.640625 0 L 3.640625 -0.65625 C 3.484375 -0.394531 3.289062 -0.203125 3.0625 -0.078125 C 2.832031 0.046875 2.554688 0.109375 2.234375 0.109375 C 1.703125 0.109375 1.269531 -0.0976562 0.9375 -0.515625 C 0.601562 -0.941406 0.4375 -1.5 0.4375 -2.1875 C 0.4375 -2.875 0.601562 -3.425781 0.9375 -3.84375 C 1.269531 -4.269531 1.703125 -4.484375 2.234375 -4.484375 C 2.554688 -4.484375 2.832031 -4.421875 3.0625 -4.296875 C 3.289062 -4.171875 3.484375 -3.976562 3.640625 -3.71875 Z M 1.1875 -2.1875 C 1.1875 -1.65625 1.296875 -1.238281 1.515625 -0.9375 C 1.734375 -0.632812 2.03125 -0.484375 2.40625 -0.484375 C 2.789062 -0.484375 3.09375 -0.632812 3.3125 -0.9375 C 3.53125 -1.238281 3.640625 -1.65625 3.640625 -2.1875 C 3.640625 -2.71875 3.53125 -3.128906 3.3125 -3.421875 C 3.09375 -3.722656 2.789062 -3.875 2.40625 -3.875 C 2.03125 -3.875 1.734375 -3.722656 1.515625 -3.421875 C 1.296875 -3.128906 1.1875 -2.71875 1.1875 -2.1875 Z M 1.1875 -2.1875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-14">
<path style="stroke:none;" d="M 4.5 -2.375 L 4.5 -2.015625 L 1.1875 -2.015625 C 1.21875 -1.523438 1.367188 -1.148438 1.640625 -0.890625 C 1.910156 -0.628906 2.28125 -0.5 2.75 -0.5 C 3.03125 -0.5 3.300781 -0.53125 3.5625 -0.59375 C 3.820312 -0.664062 4.078125 -0.769531 4.328125 -0.90625 L 4.328125 -0.21875 C 4.066406 -0.113281 3.800781 -0.0351562 3.53125 0.015625 C 3.257812 0.078125 2.988281 0.109375 2.71875 0.109375 C 2.019531 0.109375 1.460938 -0.09375 1.046875 -0.5 C 0.640625 -0.90625 0.4375 -1.453125 0.4375 -2.140625 C 0.4375 -2.859375 0.628906 -3.425781 1.015625 -3.84375 C 1.410156 -4.269531 1.9375 -4.484375 2.59375 -4.484375 C 3.175781 -4.484375 3.640625 -4.289062 3.984375 -3.90625 C 4.328125 -3.53125 4.5 -3.019531 4.5 -2.375 Z M 3.78125 -2.578125 C 3.769531 -2.972656 3.65625 -3.285156 3.4375 -3.515625 C 3.226562 -3.753906 2.945312 -3.875 2.59375 -3.875 C 2.195312 -3.875 1.875 -3.757812 1.625 -3.53125 C 1.382812 -3.300781 1.25 -2.984375 1.21875 -2.578125 Z M 3.78125 -2.578125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-15">
<path style="stroke:none;" d="M 1.53125 -0.671875 L 4.296875 -0.671875 L 4.296875 0 L 0.59375 0 L 0.59375 -0.671875 C 0.882812 -0.972656 1.289062 -1.382812 1.8125 -1.90625 C 2.332031 -2.4375 2.65625 -2.773438 2.78125 -2.921875 C 3.039062 -3.203125 3.21875 -3.441406 3.3125 -3.640625 C 3.414062 -3.835938 3.46875 -4.03125 3.46875 -4.21875 C 3.46875 -4.53125 3.359375 -4.785156 3.140625 -4.984375 C 2.921875 -5.179688 2.640625 -5.28125 2.296875 -5.28125 C 2.046875 -5.28125 1.78125 -5.234375 1.5 -5.140625 C 1.226562 -5.054688 0.9375 -4.925781 0.625 -4.75 L 0.625 -5.546875 C 0.945312 -5.679688 1.242188 -5.78125 1.515625 -5.84375 C 1.796875 -5.90625 2.050781 -5.9375 2.28125 -5.9375 C 2.882812 -5.9375 3.363281 -5.785156 3.71875 -5.484375 C 4.082031 -5.179688 4.265625 -4.78125 4.265625 -4.28125 C 4.265625 -4.039062 4.21875 -3.8125 4.125 -3.59375 C 4.03125 -3.375 3.867188 -3.117188 3.640625 -2.828125 C 3.566406 -2.753906 3.351562 -2.535156 3 -2.171875 C 2.65625 -1.816406 2.164062 -1.316406 1.53125 -0.671875 Z M 1.53125 -0.671875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-16">
<path style="stroke:none;" d="M 3.25 -3.140625 C 3.625 -3.066406 3.914062 -2.898438 4.125 -2.640625 C 4.34375 -2.390625 4.453125 -2.078125 4.453125 -1.703125 C 4.453125 -1.117188 4.253906 -0.671875 3.859375 -0.359375 C 3.460938 -0.046875 2.898438 0.109375 2.171875 0.109375 C 1.921875 0.109375 1.664062 0.0820312 1.40625 0.03125 C 1.15625 -0.0078125 0.890625 -0.078125 0.609375 -0.171875 L 0.609375 -0.9375 C 0.828125 -0.8125 1.066406 -0.710938 1.328125 -0.640625 C 1.585938 -0.578125 1.859375 -0.546875 2.140625 -0.546875 C 2.640625 -0.546875 3.019531 -0.644531 3.28125 -0.84375 C 3.539062 -1.039062 3.671875 -1.328125 3.671875 -1.703125 C 3.671875 -2.046875 3.546875 -2.3125 3.296875 -2.5 C 3.054688 -2.695312 2.722656 -2.796875 2.296875 -2.796875 L 1.625 -2.796875 L 1.625 -3.4375 L 2.328125 -3.4375 C 2.710938 -3.4375 3.007812 -3.515625 3.21875 -3.671875 C 3.425781 -3.828125 3.53125 -4.050781 3.53125 -4.34375 C 3.53125 -4.644531 3.421875 -4.875 3.203125 -5.03125 C 2.992188 -5.195312 2.691406 -5.28125 2.296875 -5.28125 C 2.078125 -5.28125 1.84375 -5.253906 1.59375 -5.203125 C 1.351562 -5.160156 1.082031 -5.085938 0.78125 -4.984375 L 0.78125 -5.6875 C 1.082031 -5.769531 1.363281 -5.832031 1.625 -5.875 C 1.882812 -5.914062 2.132812 -5.9375 2.375 -5.9375 C 2.96875 -5.9375 3.4375 -5.800781 3.78125 -5.53125 C 4.132812 -5.257812 4.3125 -4.890625 4.3125 -4.421875 C 4.3125 -4.097656 4.21875 -3.828125 4.03125 -3.609375 C 3.851562 -3.390625 3.59375 -3.234375 3.25 -3.140625 Z M 3.25 -3.140625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-17">
<path style="stroke:none;" d="M 2.734375 -5.0625 L 1.671875 -2.15625 L 3.8125 -2.15625 Z M 2.296875 -5.828125 L 3.1875 -5.828125 L 5.40625 0 L 4.59375 0 L 4.0625 -1.5 L 1.421875 -1.5 L 0.890625 0 L 0.0625 0 Z M 2.296875 -5.828125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-18">
<path style="stroke:none;" d="M 0.75 -6.078125 L 1.46875 -6.078125 L 1.46875 0 L 0.75 0 Z M 0.75 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-19">
<path style="stroke:none;" d="M 3.90625 -4.203125 L 3.90625 -3.53125 C 3.695312 -3.644531 3.488281 -3.726562 3.28125 -3.78125 C 3.082031 -3.84375 2.878906 -3.875 2.671875 -3.875 C 2.203125 -3.875 1.835938 -3.722656 1.578125 -3.421875 C 1.328125 -3.128906 1.203125 -2.71875 1.203125 -2.1875 C 1.203125 -1.65625 1.328125 -1.238281 1.578125 -0.9375 C 1.835938 -0.644531 2.203125 -0.5 2.671875 -0.5 C 2.878906 -0.5 3.082031 -0.523438 3.28125 -0.578125 C 3.488281 -0.640625 3.695312 -0.722656 3.90625 -0.828125 L 3.90625 -0.171875 C 3.707031 -0.078125 3.5 -0.0078125 3.28125 0.03125 C 3.0625 0.0820312 2.832031 0.109375 2.59375 0.109375 C 1.9375 0.109375 1.410156 -0.09375 1.015625 -0.5 C 0.628906 -0.914062 0.4375 -1.476562 0.4375 -2.1875 C 0.4375 -2.894531 0.632812 -3.453125 1.03125 -3.859375 C 1.425781 -4.273438 1.960938 -4.484375 2.640625 -4.484375 C 2.859375 -4.484375 3.070312 -4.457031 3.28125 -4.40625 C 3.5 -4.363281 3.707031 -4.296875 3.90625 -4.203125 Z M 3.90625 -4.203125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-20">
<path style="stroke:none;" d="M 4.390625 -2.640625 L 4.390625 0 L 3.671875 0 L 3.671875 -2.625 C 3.671875 -3.03125 3.585938 -3.335938 3.421875 -3.546875 C 3.265625 -3.753906 3.023438 -3.859375 2.703125 -3.859375 C 2.316406 -3.859375 2.007812 -3.734375 1.78125 -3.484375 C 1.5625 -3.234375 1.453125 -2.894531 1.453125 -2.46875 L 1.453125 0 L 0.734375 0 L 0.734375 -6.078125 L 1.453125 -6.078125 L 1.453125 -3.703125 C 1.617188 -3.960938 1.816406 -4.15625 2.046875 -4.28125 C 2.285156 -4.414062 2.554688 -4.484375 2.859375 -4.484375 C 3.367188 -4.484375 3.75 -4.328125 4 -4.015625 C 4.257812 -3.703125 4.390625 -3.242188 4.390625 -2.640625 Z M 4.390625 -2.640625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-21">
<path style="stroke:none;" d="M 2.96875 -6.078125 L 2.96875 -5.484375 L 2.28125 -5.484375 C 2.019531 -5.484375 1.835938 -5.429688 1.734375 -5.328125 C 1.640625 -5.222656 1.59375 -5.035156 1.59375 -4.765625 L 1.59375 -4.375 L 2.78125 -4.375 L 2.78125 -3.8125 L 1.59375 -3.8125 L 1.59375 0 L 0.875 0 L 0.875 -3.8125 L 0.1875 -3.8125 L 0.1875 -4.375 L 0.875 -4.375 L 0.875 -4.6875 C 0.875 -5.164062 0.984375 -5.515625 1.203125 -5.734375 C 1.429688 -5.960938 1.796875 -6.078125 2.296875 -6.078125 Z M 2.96875 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-22">
<path style="stroke:none;" d="M 0.734375 -6.078125 L 1.453125 -6.078125 L 1.453125 -2.484375 L 3.59375 -4.375 L 4.515625 -4.375 L 2.1875 -2.328125 L 4.609375 0 L 3.671875 0 L 1.453125 -2.140625 L 1.453125 0 L 0.734375 0 Z M 0.734375 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-23">
<path style="stroke:none;" d="M 0.34375 -4.375 L 1.0625 -4.375 L 1.953125 -0.96875 L 2.84375 -4.375 L 3.703125 -4.375 L 4.59375 -0.96875 L 5.484375 -4.375 L 6.203125 -4.375 L 5.0625 0 L 4.21875 0 L 3.28125 -3.59375 L 2.328125 0 L 1.484375 0 Z M 0.34375 -4.375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-24">
<path style="stroke:none;" d="M 4.28125 -5.640625 L 4.28125 -4.875 C 3.976562 -5.019531 3.691406 -5.125 3.421875 -5.1875 C 3.160156 -5.257812 2.910156 -5.296875 2.671875 -5.296875 C 2.234375 -5.296875 1.898438 -5.210938 1.671875 -5.046875 C 1.441406 -4.878906 1.328125 -4.644531 1.328125 -4.34375 C 1.328125 -4.082031 1.398438 -3.882812 1.546875 -3.75 C 1.703125 -3.625 2 -3.519531 2.4375 -3.4375 L 2.90625 -3.34375 C 3.5 -3.226562 3.9375 -3.03125 4.21875 -2.75 C 4.5 -2.46875 4.640625 -2.085938 4.640625 -1.609375 C 4.640625 -1.046875 4.445312 -0.617188 4.0625 -0.328125 C 3.6875 -0.0351562 3.132812 0.109375 2.40625 0.109375 C 2.125 0.109375 1.828125 0.078125 1.515625 0.015625 C 1.203125 -0.046875 0.878906 -0.140625 0.546875 -0.265625 L 0.546875 -1.078125 C 0.867188 -0.890625 1.179688 -0.75 1.484375 -0.65625 C 1.796875 -0.570312 2.101562 -0.53125 2.40625 -0.53125 C 2.851562 -0.53125 3.195312 -0.617188 3.4375 -0.796875 C 3.6875 -0.972656 3.8125 -1.222656 3.8125 -1.546875 C 3.8125 -1.835938 3.722656 -2.0625 3.546875 -2.21875 C 3.367188 -2.382812 3.082031 -2.507812 2.6875 -2.59375 L 2.203125 -2.6875 C 1.609375 -2.800781 1.179688 -2.984375 0.921875 -3.234375 C 0.660156 -3.484375 0.53125 -3.832031 0.53125 -4.28125 C 0.53125 -4.789062 0.710938 -5.191406 1.078125 -5.484375 C 1.441406 -5.785156 1.941406 -5.9375 2.578125 -5.9375 C 2.847656 -5.9375 3.125 -5.910156 3.40625 -5.859375 C 3.695312 -5.816406 3.988281 -5.742188 4.28125 -5.640625 Z M 4.28125 -5.640625 "/>
</symbol>
</g>
<clipPath id="clip1">
<path d="M 1149.457031 48 L 1191 48 L 1191 59 L 1149.457031 59 Z M 1149.457031 48 "/>
</clipPath>
<clipPath id="clip2">
<path d="M 168.484375 80 L 210 80 L 210 91 L 168.484375 91 Z M 168.484375 80 "/>
</clipPath>
<clipPath id="clip3">
<path d="M 945.597656 80 L 987 80 L 987 91 L 945.597656 91 Z M 945.597656 80 "/>
</clipPath>
<clipPath id="clip4">
<path d="M 250.011719 69 L 303 69 L 303 78 L 250.011719 78 Z M 250.011719 69 "/>
</clipPath>
<clipPath id="clip5">
<path d="M 979.003906 69 L 1003.683594 69 L 1003.683594 77 L 979.003906 77 Z M 979.003906 69 "/>
</clipPath>
<clipPath id="clip6">
<path d="M 1018.117188 112 L 1060 112 L 1060 123 L 1018.117188 123 Z M 1018.117188 112 "/>
</clipPath>
<clipPath id="clip7">
<path d="M 583.375 101 L 637 101 L 637 110 L 583.375 110 Z M 583.375 101 "/>
</clipPath>
<clipPath id="clip8">
<path d="M 1049.300781 101 L 1070.394531 101 L 1070.394531 109 L 1049.300781 109 Z M 1049.300781 101 "/>
</clipPath>
<clipPath id="clip9">
<path d="M 1082.304688 144 L 1124 144 L 1124 155 L 1082.304688 155 Z M 1082.304688 144 "/>
</clipPath>
<clipPath id="clip10">
<path d="M 819.433594 133 L 873 133 L 873 142 L 819.433594 142 Z M 819.433594 133 "/>
</clipPath>
<clipPath id="clip11">
<path d="M 1112 133 L 1132.316406 133 L 1132.316406 141 L 1112 141 Z M 1112 133 "/>
</clipPath>
<clipPath id="clip12">
<path d="M 901.039062 37 L 985 37 L 985 45 L 901.039062 45 Z M 901.039062 37 "/>
</clipPath>
<clipPath id="clip13">
<path d="M 1179.296875 37 L 1200.945312 37 L 1200.945312 45 L 1179.296875 45 Z M 1179.296875 37 "/>
</clipPath>
</defs>
<g id="surface2">
<rect x="0" y="0" width="1280" height="184" style="fill:rgb(90%,90%,90%);fill-opacity:1;stroke:none;"/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 4 0 L 4 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 195.652344 0 L 195.652344 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 387.304688 0 L 387.304688 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 578.957031 0 L 578.957031 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 770.613281 0 L 770.613281 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 962.265625 0 L 962.265625 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1153.917969 0 L 1153.917969 184 "/>
<g style="fill:rgb(40%,40%,40%);fill-opacity:1;">
<use xlink:href="#glyph0-1" x="4" y="179.503906"/>
<use xlink:href="#glyph0-2" x="11.582031" y="179.503906"/>
<use xlink:href="#glyph0-3" x="18.935547" y="179.503906"/>
<use xlink:href="#glyph0-4" x="25.533203" y="179.503906"/>
<use xlink:href="#glyph0-5" x="33.138672" y="179.503906"/>
<use xlink:href="#glyph0-6" x="36.953125" y="179.503906"/>
<use xlink:href="#glyph0-7" x="44.570312" y="179.503906"/>
<use xlink:href="#glyph0-8" x="49.503906" y="179.503906"/>
<use xlink:href="#glyph0-9" x="52.837891" y="179.503906"/>
<use xlink:href="#glyph0-5" x="60.455078" y="179.503906"/>
<use xlink:href="#glyph0-9" x="64.269531" y="179.503906"/>
<use xlink:href="#glyph0-8" x="71.886719" y="179.503906"/>
<use xlink:href="#glyph0-10" x="75.220703" y="179.503906"/>
<use xlink:href="#glyph0-8" x="82.322266" y="179.503906"/>
<use xlink:href="#glyph0-11" x="85.65625" y="179.503906"/>
<use xlink:href="#glyph0-8" x="91.908203" y="179.503906"/>
<use xlink:href="#glyph0-12" x="95.242188" y="179.503906"/>
<use xlink:href="#glyph0-13" x="102.583984" y="179.503906"/>
<use xlink:href="#glyph0-14" x="110.189453" y="179.503906"/>
<use xlink:href="#glyph0-5" x="114.232422" y="179.503906"/>
<use xlink:href="#glyph0-15" x="118.046875" y="179.503906"/>
<use xlink:href="#glyph0-16" x="125.681641" y="179.503906"/>
<use xlink:href="#glyph0-5" x="133.316406" y="179.503906"/>
<use xlink:href="#glyph0-17" x="137.130859" y="179.503906"/>
<use xlink:href="#glyph0-11" x="144.736328" y="179.503906"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 10.746094 46 L 18.640625 46 L 18.640625 60 L 10.746094 60 Z M 10.746094 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 18.640625 46 L 59.882812 46 L 59.882812 60 L 18.640625 60 Z M 18.640625 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 59.886719 46 L 491.21875 46 L 491.21875 60 L 59.886719 60 Z M 59.886719 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 491.21875 46 L 523.03125 46 L 523.03125 60 L 491.21875 60 Z M 491.21875 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 523.035156 46 L 618.25 46 L 618.25 60 L 523.035156 60 Z M 523.035156 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 618.246094 46 L 621.214844 46 L 621.214844 60 L 618.246094 60 Z M 618.246094 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 621.21875 46 L 864.0625 46 L 864.0625 60 L 621.21875 60 Z M 621.21875 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 864.0625 46 L 871 46 L 871 60 L 864.0625 60 Z M 864.0625 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 871 46 L 918.414062 46 L 918.414062 60 L 871 60 Z M 871 46 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="873" y="58"/>
<use xlink:href="#glyph0-7" x="877.705078" y="58"/>
<use xlink:href="#glyph0-2" x="882.638672" y="58"/>
<use xlink:href="#glyph0-3" x="889.992188" y="58"/>
<use xlink:href="#glyph0-19" x="896.589844" y="58"/>
<use xlink:href="#glyph0-20" x="903.972656" y="58"/>
<use xlink:href="#glyph0-13" x="907.306641" y="58"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 918.414062 46 L 927.882812 46 L 927.882812 60 L 918.414062 60 Z M 918.414062 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 927.882812 46 L 1134.214844 46 L 1134.214844 60 L 927.882812 60 Z M 927.882812 46 "/>
<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
<use xlink:href="#glyph0-21" x="929.882812" y="58"/>
<use xlink:href="#glyph0-22" x="937.5" y="58"/>
<use xlink:href="#glyph0-8" x="947.314453" y="58"/>
<use xlink:href="#glyph0-18" x="950.648438" y="58"/>
<use xlink:href="#glyph0-3" x="955.353516" y="58"/>
<use xlink:href="#glyph0-4" x="961.951172" y="58"/>
<use xlink:href="#glyph0-23" x="969.556641" y="58"/>
<use xlink:href="#glyph0-2" x="973.371094" y="58"/>
<use xlink:href="#glyph0-22" x="980.724609" y="58"/>
<use xlink:href="#glyph0-2" x="990.539062" y="58"/>
<use xlink:href="#glyph0-8" x="997.892578" y="58"/>
<use xlink:href="#glyph0-18" x="1001.226562" y="58"/>
<use xlink:href="#glyph0-24" x="1005.931641" y="58"/>
<use xlink:href="#glyph0-8" x="1011.931641" y="58"/>
<use xlink:href="#glyph0-9" x="1015.265625" y="58"/>
<use xlink:href="#glyph0-20" x="1022.882812" y="58"/>
<use xlink:href="#glyph0-19" x="1026.216797" y="58"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1134.214844 46 L 1147.457031 46 L 1147.457031 60 L 1134.214844 60 Z M 1134.214844 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1147.457031 46 L 1196.730469 46 L 1196.730469 60 L 1147.457031 60 Z M 1147.457031 46 "/>
<g clip-path="url(#clip1)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="1149.457031" y="58"/>
<use xlink:href="#glyph0-7" x="1154.162109" y="58"/>
<use xlink:href="#glyph0-2" x="1159.095703" y="58"/>
<use xlink:href="#glyph0-3" x="1166.449219" y="58"/>
<use xlink:href="#glyph0-19" x="1173.046875" y="58"/>
<use xlink:href="#glyph0-20" x="1180.429688" y="58"/>
<use xlink:href="#glyph0-13" x="1183.763672" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1196.730469 46 L 1200.945312 46 L 1200.945312 60 L 1196.730469 60 Z M 1196.730469 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 59.886719 78 L 62.914062 78 L 62.914062 92 L 59.886719 92 Z M 59.886719 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 62.914062 78 L 166.484375 78 L 166.484375 92 L 62.914062 92 Z M 62.914062 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 166.484375 78 L 471.960938 78 L 471.960938 92 L 166.484375 92 Z M 166.484375 78 "/>
<g clip-path="url(#clip2)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="168.484375" y="90"/>
<use xlink:href="#glyph0-7" x="173.189453" y="90"/>
<use xlink:href="#glyph0-2" x="178.123047" y="90"/>
<use xlink:href="#glyph0-3" x="185.476562" y="90"/>
<use xlink:href="#glyph0-19" x="192.074219" y="90"/>
<use xlink:href="#glyph0-20" x="199.457031" y="90"/>
<use xlink:href="#glyph0-13" x="202.791016" y="90"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 471.957031 78 L 483.074219 78 L 483.074219 92 L 471.957031 92 Z M 471.957031 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 483.074219 78 L 932.367188 78 L 932.367188 92 L 483.074219 92 Z M 483.074219 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 932.367188 78 L 943.597656 78 L 943.597656 92 L 932.367188 92 Z M 932.367188 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 943.597656 78 L 996.683594 78 L 996.683594 92 L 943.597656 92 Z M 943.597656 78 "/>
<g clip-path="url(#clip3)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="945.597656" y="90"/>
<use xlink:href="#glyph0-7" x="950.302734" y="90"/>
<use xlink:href="#glyph0-2" x="955.236328" y="90"/>
<use xlink:href="#glyph0-3" x="962.589844" y="90"/>
<use xlink:href="#glyph0-19" x="969.1875" y="90"/>
<use xlink:href="#glyph0-20" x="976.570312" y="90"/>
<use xlink:href="#glyph0-13" x="979.904297" y="90"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 996.683594 78 L 1006.457031 78 L 1006.457031 92 L 996.683594 92 Z M 996.683594 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1006.460938 78 L 1003.683594 78 L 1003.683594 92 L 1006.460938 92 Z M 1006.460938 78 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 248.011719 81 L 248.011719 75 "/>
<g clip-path="url(#clip4)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="250.011719" y="76"/>
<use xlink:href="#glyph1-2" x="252.371094" y="76"/>
<use xlink:href="#glyph1-3" x="257.265625" y="76"/>
<use xlink:href="#glyph1-4" x="262.34375" y="76"/>
<use xlink:href="#glyph1-5" x="264.886719" y="76"/>
<use xlink:href="#glyph1-4" x="269.976562" y="76"/>
<use xlink:href="#glyph1-6" x="272.519531" y="76"/>
<use xlink:href="#glyph1-7" x="276.6875" y="76"/>
<use xlink:href="#glyph1-8" x="279.824219" y="76"/>
<use xlink:href="#glyph1-9" x="284.726562" y="76"/>
<use xlink:href="#glyph1-7" x="288.015625" y="76"/>
<use xlink:href="#glyph1-10" x="291.152344" y="76"/>
<use xlink:href="#glyph1-11" x="293.375" y="76"/>
<use xlink:href="#glyph1-12" x="298.445312" y="76"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 977.003906 81 L 977.003906 75 "/>
<g clip-path="url(#clip5)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-5" x="979.003906" y="76"/>
<use xlink:href="#glyph1-4" x="984.09375" y="76"/>
<use xlink:href="#glyph1-13" x="986.636719" y="76"/>
<use xlink:href="#glyph1-2" x="991.714844" y="76"/>
<use xlink:href="#glyph1-11" x="996.609375" y="76"/>
<use xlink:href="#glyph1-14" x="1001.679688" y="76"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 59.886719 46 L 59.886719 92 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 523.035156 110 L 525.679688 110 L 525.679688 124 L 523.035156 124 Z M 523.035156 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 525.679688 110 L 544.980469 110 L 544.980469 124 L 525.679688 124 Z M 525.679688 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 544.976562 110 L 611.632812 110 L 611.632812 124 L 544.976562 124 Z M 544.976562 110 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="546.976562" y="122"/>
<use xlink:href="#glyph0-7" x="551.681641" y="122"/>
<use xlink:href="#glyph0-2" x="556.615234" y="122"/>
<use xlink:href="#glyph0-3" x="563.96875" y="122"/>
<use xlink:href="#glyph0-19" x="570.566406" y="122"/>
<use xlink:href="#glyph0-20" x="577.949219" y="122"/>
<use xlink:href="#glyph0-13" x="581.283203" y="122"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 611.636719 110 L 615.625 110 L 615.625 124 L 611.636719 124 Z M 611.636719 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 615.621094 110 L 1006.457031 110 L 1006.457031 124 L 615.621094 124 Z M 615.621094 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1006.460938 110 L 1016.121094 110 L 1016.121094 124 L 1006.460938 124 Z M 1006.460938 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1016.117188 110 L 1065.160156 110 L 1065.160156 124 L 1016.117188 124 Z M 1016.117188 110 "/>
<g clip-path="url(#clip6)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="1018.117188" y="122"/>
<use xlink:href="#glyph0-7" x="1022.822266" y="122"/>
<use xlink:href="#glyph0-2" x="1027.755859" y="122"/>
<use xlink:href="#glyph0-3" x="1035.109375" y="122"/>
<use xlink:href="#glyph0-19" x="1041.707031" y="122"/>
<use xlink:href="#glyph0-20" x="1049.089844" y="122"/>
<use xlink:href="#glyph0-13" x="1052.423828" y="122"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1065.164062 110 L 1071.8125 110 L 1071.8125 124 L 1065.164062 124 Z M 1065.164062 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1071.8125 110 L 1070.394531 110 L 1070.394531 124 L 1071.8125 124 Z M 1071.8125 110 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 581.375 113 L 581.375 107 "/>
<g clip-path="url(#clip7)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="583.375" y="108"/>
<use xlink:href="#glyph1-2" x="585.734375" y="108"/>
<use xlink:href="#glyph1-3" x="590.628906" y="108"/>
<use xlink:href="#glyph1-4" x="595.707031" y="108"/>
<use xlink:href="#glyph1-15" x="598.25" y="108"/>
<use xlink:href="#glyph1-4" x="603.339844" y="108"/>
<use xlink:href="#glyph1-6" x="605.882812" y="108"/>
<use xlink:href="#glyph1-7" x="610.050781" y="108"/>
<use xlink:href="#glyph1-8" x="613.1875" y="108"/>
<use xlink:href="#glyph1-9" x="618.089844" y="108"/>
<use xlink:href="#glyph1-7" x="621.378906" y="108"/>
<use xlink:href="#glyph1-10" x="624.515625" y="108"/>
<use xlink:href="#glyph1-11" x="626.738281" y="108"/>
<use xlink:href="#glyph1-12" x="631.808594" y="108"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1047.300781 113 L 1047.300781 107 "/>
<g clip-path="url(#clip8)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-15" x="1049.300781" y="108"/>
<use xlink:href="#glyph1-4" x="1054.390625" y="108"/>
<use xlink:href="#glyph1-13" x="1056.933594" y="108"/>
<use xlink:href="#glyph1-2" x="1062.011719" y="108"/>
<use xlink:href="#glyph1-11" x="1066.90625" y="108"/>
<use xlink:href="#glyph1-14" x="1071.976562" y="108"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 523.035156 46 L 523.035156 124 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 621.21875 142 L 622.960938 142 L 622.960938 156 L 621.21875 156 Z M 621.21875 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 622.960938 142 L 634.785156 142 L 634.785156 156 L 622.960938 156 Z M 622.960938 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 634.785156 142 L 838.511719 142 L 838.511719 156 L 634.785156 156 Z M 634.785156 142 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="636.785156" y="154"/>
<use xlink:href="#glyph0-7" x="641.490234" y="154"/>
<use xlink:href="#glyph0-2" x="646.423828" y="154"/>
<use xlink:href="#glyph0-3" x="653.777344" y="154"/>
<use xlink:href="#glyph0-19" x="660.375" y="154"/>
<use xlink:href="#glyph0-20" x="667.757812" y="154"/>
<use xlink:href="#glyph0-13" x="671.091797" y="154"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 838.515625 142 L 861.878906 142 L 861.878906 156 L 838.515625 156 Z M 838.515625 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 861.875 142 L 1071.8125 142 L 1071.8125 156 L 861.875 156 Z M 861.875 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1071.8125 142 L 1080.300781 142 L 1080.300781 156 L 1071.8125 156 Z M 1071.8125 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1080.304688 142 L 1126.570312 142 L 1126.570312 156 L 1080.304688 156 Z M 1080.304688 142 "/>
<g clip-path="url(#clip9)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="1082.304688" y="154"/>
<use xlink:href="#glyph0-7" x="1087.009766" y="154"/>
<use xlink:href="#glyph0-2" x="1091.943359" y="154"/>
<use xlink:href="#glyph0-3" x="1099.296875" y="154"/>
<use xlink:href="#glyph0-19" x="1105.894531" y="154"/>
<use xlink:href="#glyph0-20" x="1113.277344" y="154"/>
<use xlink:href="#glyph0-13" x="1116.611328" y="154"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1126.566406 142 L 1134.214844 142 L 1134.214844 156 L 1126.566406 156 Z M 1126.566406 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1134.214844 142 L 1132.316406 142 L 1132.316406 156 L 1134.214844 156 Z M 1134.214844 142 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 817.433594 145 L 817.433594 139 "/>
<g clip-path="url(#clip10)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="819.433594" y="140"/>
<use xlink:href="#glyph1-2" x="821.792969" y="140"/>
<use xlink:href="#glyph1-3" x="826.6875" y="140"/>
<use xlink:href="#glyph1-4" x="831.765625" y="140"/>
<use xlink:href="#glyph1-16" x="834.308594" y="140"/>
<use xlink:href="#glyph1-4" x="839.398438" y="140"/>
<use xlink:href="#glyph1-6" x="841.941406" y="140"/>
<use xlink:href="#glyph1-7" x="846.109375" y="140"/>
<use xlink:href="#glyph1-8" x="849.246094" y="140"/>
<use xlink:href="#glyph1-9" x="854.148438" y="140"/>
<use xlink:href="#glyph1-7" x="857.4375" y="140"/>
<use xlink:href="#glyph1-10" x="860.574219" y="140"/>
<use xlink:href="#glyph1-11" x="862.796875" y="140"/>
<use xlink:href="#glyph1-12" x="867.867188" y="140"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1109.605469 145 L 1109.605469 139 "/>
<g clip-path="url(#clip11)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-16" x="1111.605469" y="140"/>
<use xlink:href="#glyph1-4" x="1116.695312" y="140"/>
<use xlink:href="#glyph1-13" x="1119.238281" y="140"/>
<use xlink:href="#glyph1-2" x="1124.316406" y="140"/>
<use xlink:href="#glyph1-11" x="1129.210938" y="140"/>
<use xlink:href="#glyph1-14" x="1134.28125" y="140"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 621.21875 46 L 621.21875 156 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 899.039062 49 L 899.039062 43 "/>
<g clip-path="url(#clip12)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-17" x="901.039062" y="44"/>
<use xlink:href="#glyph1-18" x="906.511719" y="44"/>
<use xlink:href="#glyph1-18" x="908.734375" y="44"/>
<use xlink:href="#glyph1-4" x="910.957031" y="44"/>
<use xlink:href="#glyph1-19" x="913.5" y="44"/>
<use xlink:href="#glyph1-20" x="917.898438" y="44"/>
<use xlink:href="#glyph1-10" x="922.96875" y="44"/>
<use xlink:href="#glyph1-18" x="925.191406" y="44"/>
<use xlink:href="#glyph1-13" x="927.414062" y="44"/>
<use xlink:href="#glyph1-4" x="932.492188" y="44"/>
<use xlink:href="#glyph1-21" x="935.035156" y="44"/>
<use xlink:href="#glyph1-10" x="937.851562" y="44"/>
<use xlink:href="#glyph1-3" x="940.074219" y="44"/>
<use xlink:href="#glyph1-14" x="945.152344" y="44"/>
<use xlink:href="#glyph1-9" x="950.074219" y="44"/>
<use xlink:href="#glyph1-6" x="953.363281" y="44"/>
<use xlink:href="#glyph1-4" x="957.53125" y="44"/>
<use xlink:href="#glyph1-21" x="960.074219" y="44"/>
<use xlink:href="#glyph1-2" x="962.890625" y="44"/>
<use xlink:href="#glyph1-9" x="967.785156" y="44"/>
<use xlink:href="#glyph1-22" x="971.074219" y="44"/>
<use xlink:href="#glyph1-14" x="975.707031" y="44"/>
<use xlink:href="#glyph1-13" x="980.628906" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 46.675781 36 L 42.675781 36 L 42.675781 160 L 46.675781 160 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1138.78125 36 L 1142.78125 36 L 1142.78125 160 L 1138.78125 160 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-6" x="44.675781" y="44"/>
<use xlink:href="#glyph1-23" x="48.84375" y="44"/>
<use xlink:href="#glyph1-10" x="55.386719" y="44"/>
<use xlink:href="#glyph1-7" x="57.609375" y="44"/>
<use xlink:href="#glyph1-19" x="60.746094" y="44"/>
<use xlink:href="#glyph1-20" x="65.144531" y="44"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1177.296875 49 L 1177.296875 43 "/>
<g clip-path="url(#clip13)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-24" x="1179.296875" y="44"/>
<use xlink:href="#glyph1-23" x="1184.375" y="44"/>
<use xlink:href="#glyph1-10" x="1190.917969" y="44"/>
<use xlink:href="#glyph1-7" x="1193.140625" y="44"/>
<use xlink:href="#glyph1-19" x="1196.277344" y="44"/>
<use xlink:href="#glyph1-20" x="1200.675781" y="44"/>
<use xlink:href="#glyph1-4" x="1205.746094" y="44"/>
<use xlink:href="#glyph1-10" x="1208.289062" y="44"/>
<use xlink:href="#glyph1-6" x="1210.511719" y="44"/>
<use xlink:href="#glyph1-4" x="1214.679688" y="44"/>
<use xlink:href="#glyph1-21" x="1217.222656" y="44"/>
<use xlink:href="#glyph1-10" x="1220.039062" y="44"/>
<use xlink:href="#glyph1-11" x="1222.261719" y="44"/>
<use xlink:href="#glyph1-10" x="1227.332031" y="44"/>
<use xlink:href="#glyph1-6" x="1229.554688" y="44"/>
<use xlink:href="#glyph1-20" x="1233.722656" y="44"/>
<use xlink:href="#glyph1-14" x="1238.792969" y="44"/>
<use xlink:href="#glyph1-13" x="1243.714844" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 10.746094 46 L 10.746094 60 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 61 KiB

3
dune
View File

@ -1,5 +1,6 @@
(mdx (mdx
(package eio_main) (package eio_main)
(packages eio_main) (deps (package eio_main) (package kcas) (env_var "EIO_BACKEND"))
(preludes doc/prelude.ml) (preludes doc/prelude.ml)
(enabled_if (<> %{os_type} "Win32"))
(files README.md)) (files README.md))

View File

@ -1,6 +1,7 @@
(lang dune 2.9) (lang dune 3.9)
(name eio) (name eio)
(formatting disabled) (formatting disabled)
(subst disabled)
(generate_opam_files true) (generate_opam_files true)
(source (github ocaml-multicore/eio)) (source (github ocaml-multicore/eio))
(license ISC) (license ISC)
@ -11,48 +12,67 @@
(name eio) (name eio)
(synopsis "Effect-based direct-style IO API for OCaml") (synopsis "Effect-based direct-style IO API for OCaml")
(description "An effect-based IO API for multicore OCaml with fibers.") (description "An effect-based IO API for multicore OCaml with fibers.")
(conflicts (seq (< 0.3)))
(depends (depends
(ocaml (>= 4.12.0)) (ocaml (>= 5.2.0))
base-domains (bigstringaf (>= 0.9.0))
(cstruct (>= 6.0.1)) (cstruct (>= 6.0.1))
lwt-dllist lwt-dllist
(optint (>= 0.1.0)) (optint (>= 0.1.0))
(psq (>= 0.2.0)) (psq (>= 0.2.0))
(fmt (>= 0.8.9)) (fmt (>= 0.8.9))
(astring (and (>= 0.8.5) :with-test)) (hmap (>= 0.8.1))
(domain-local-await (>= 0.1.0))
(crowbar (and (>= 0.2) :with-test)) (crowbar (and (>= 0.2) :with-test))
(mtime (>= 1.2.0)) (mtime (>= 2.0.0))
(alcotest (and (>= 1.4.0) :with-test)))) (mdx (and (>= 2.4.1) :with-test))
(dscheck (and (>= 0.1.0) :with-test))))
(package (package
(name eio_linux) (name eio_linux)
(synopsis "Eio implementation for Linux using io-uring") (synopsis "Eio implementation for Linux using io-uring")
(description "An eio implementation for Linux using io-uring.") (description "An Eio implementation for Linux using io-uring.")
(allow_empty) ; Work-around for dune bug #6938
(depends (depends
(alcotest (and (>= 1.4.0) :with-test)) (alcotest (and (>= 1.7.0) :with-test))
base-domains
(eio (= :version)) (eio (= :version))
(mdx (and (>= 1.10.0) :with-test)) (mdx (and (>= 2.4.1) :with-test))
(logs (>= 0.7.0)) (logs (and (>= 0.7.0) :with-test))
(fmt (>= 0.8.9)) (fmt (>= 0.8.9))
(uring (>= 0.3)))) (cmdliner (and (>= 1.1.0) :with-test))
(uring (>= 0.9))))
(package (package
(name eio_luv) (name eio_posix)
(synopsis "Eio implementation using luv (libuv)") (allow_empty) ; Work-around for dune bug #6938
(description "An eio implementation for most platforms, using luv.") (synopsis "Eio implementation for POSIX systems")
(description "An Eio implementation for most Unix-like platforms")
(depends (depends
base-domains
(eio (= :version)) (eio (= :version))
(luv (>= 0.5.11)) (iomux (>= 0.2))
(luv_unix (>= 0.5.0)) (mdx (and (>= 2.4.1) :with-test))
(mdx (and (>= 1.10.0) :with-test)) (conf-bash :with-test)
(logs (>= 0.7.0))
(fmt (>= 0.8.9)))) (fmt (>= 0.8.9))))
(package
(name eio_windows)
(synopsis "Eio implementation for Windows")
(description "An Eio implementation using OCaml's Unix.select")
(allow_empty) ; Work-around for dune bug #6938
(depends
(eio (= :version))
(fmt (>= 0.8.9))
(kcas (and (>= 0.3.0) :with-test))
(alcotest (and (>= 1.7.0) :with-test))))
(package (package
(name eio_main) (name eio_main)
(synopsis "Effect-based direct-style IO mainloop for OCaml") (synopsis "Effect-based direct-style IO mainloop for OCaml")
(description "Selects an appropriate Eio backend for the current platform.") (description "Selects an appropriate Eio backend for the current platform.")
(depends (depends
(eio_linux (and (= :version) (= :os "linux"))) (mdx (and (>= 2.4.1) :with-test))
(mdx (and (>= 1.10.0) :with-test)) (kcas (and (>= 0.3.0) :with-test))
(eio_luv (= :version)))) (yojson (and (>= 2.0.2) :with-test))
(using mdx 0.1) (eio_linux (and
(= :version)
(= :os "linux")
(or (<> :os-distribution "centos") (> :os-version 7))))
(eio_posix (and (= :version) (<> :os "win32")))
(eio_windows (and (= :version) (= :os "win32")))))
(using mdx 0.2)

View File

@ -9,22 +9,26 @@ homepage: "https://github.com/ocaml-multicore/eio"
doc: "https://ocaml-multicore.github.io/eio/" doc: "https://ocaml-multicore.github.io/eio/"
bug-reports: "https://github.com/ocaml-multicore/eio/issues" bug-reports: "https://github.com/ocaml-multicore/eio/issues"
depends: [ depends: [
"dune" {>= "2.9"} "dune" {>= "3.9"}
"ocaml" {>= "4.12.0"} "ocaml" {>= "5.2.0"}
"base-domains" "bigstringaf" {>= "0.9.0"}
"cstruct" {>= "6.0.1"} "cstruct" {>= "6.0.1"}
"lwt-dllist" "lwt-dllist"
"optint" {>= "0.1.0"} "optint" {>= "0.1.0"}
"psq" {>= "0.2.0"} "psq" {>= "0.2.0"}
"fmt" {>= "0.8.9"} "fmt" {>= "0.8.9"}
"astring" {>= "0.8.5" & with-test} "hmap" {>= "0.8.1"}
"domain-local-await" {>= "0.1.0"}
"crowbar" {>= "0.2" & with-test} "crowbar" {>= "0.2" & with-test}
"mtime" {>= "1.2.0"} "mtime" {>= "2.0.0"}
"alcotest" {>= "1.4.0" & with-test} "mdx" {>= "2.4.1" & with-test}
"dscheck" {>= "0.1.0" & with-test}
"odoc" {with-doc} "odoc" {with-doc}
] ]
conflicts: [
"seq" {< "0.3"}
]
build: [ build: [
["dune" "subst"] {dev}
[ [
"dune" "dune"
"build" "build"
@ -32,11 +36,9 @@ build: [
name name
"-j" "-j"
jobs jobs
"--promote-install-files=false"
"@install" "@install"
"@runtest" {with-test} "@runtest" {with-test}
"@doc" {with-doc} "@doc" {with-doc}
] ]
["dune" "install" "-p" name "--create-install-files" name]
] ]
dev-repo: "git+https://github.com/ocaml-multicore/eio.git" dev-repo: "git+https://github.com/ocaml-multicore/eio.git"

View File

@ -1,7 +1,7 @@
# This file is generated by dune, edit dune-project instead # This file is generated by dune, edit dune-project instead
opam-version: "2.0" opam-version: "2.0"
synopsis: "Eio implementation for Linux using io-uring" synopsis: "Eio implementation for Linux using io-uring"
description: "An eio implementation for Linux using io-uring." description: "An Eio implementation for Linux using io-uring."
maintainer: ["anil@recoil.org"] maintainer: ["anil@recoil.org"]
authors: ["Anil Madhavapeddy" "Thomas Leonard"] authors: ["Anil Madhavapeddy" "Thomas Leonard"]
license: "ISC" license: "ISC"
@ -9,18 +9,17 @@ homepage: "https://github.com/ocaml-multicore/eio"
doc: "https://ocaml-multicore.github.io/eio/" doc: "https://ocaml-multicore.github.io/eio/"
bug-reports: "https://github.com/ocaml-multicore/eio/issues" bug-reports: "https://github.com/ocaml-multicore/eio/issues"
depends: [ depends: [
"dune" {>= "2.9"} "dune" {>= "3.9"}
"alcotest" {>= "1.4.0" & with-test} "alcotest" {>= "1.7.0" & with-test}
"base-domains"
"eio" {= version} "eio" {= version}
"mdx" {>= "1.10.0" & with-test} "mdx" {>= "2.4.1" & with-test}
"logs" {>= "0.7.0"} "logs" {>= "0.7.0" & with-test}
"fmt" {>= "0.8.9"} "fmt" {>= "0.8.9"}
"uring" {>= "0.3"} "cmdliner" {>= "1.1.0" & with-test}
"uring" {>= "0.9"}
"odoc" {with-doc} "odoc" {with-doc}
] ]
build: [ build: [
["dune" "subst"] {dev}
[ [
"dune" "dune"
"build" "build"
@ -28,11 +27,10 @@ build: [
name name
"-j" "-j"
jobs jobs
"--promote-install-files=false"
"@install" "@install"
"@runtest" {with-test} "@runtest" {with-test}
"@doc" {with-doc} "@doc" {with-doc}
] ]
["dune" "install" "-p" name "--create-install-files" name]
] ]
dev-repo: "git+https://github.com/ocaml-multicore/eio.git" dev-repo: "git+https://github.com/ocaml-multicore/eio.git"
available: [os = "linux"]

1
eio_linux.opam.template Normal file
View File

@ -0,0 +1 @@
available: [os = "linux"]

View File

@ -9,14 +9,18 @@ homepage: "https://github.com/ocaml-multicore/eio"
doc: "https://ocaml-multicore.github.io/eio/" doc: "https://ocaml-multicore.github.io/eio/"
bug-reports: "https://github.com/ocaml-multicore/eio/issues" bug-reports: "https://github.com/ocaml-multicore/eio/issues"
depends: [ depends: [
"dune" {>= "2.9"} "dune" {>= "3.9"}
"eio_linux" {= version & os = "linux"} "mdx" {>= "2.4.1" & with-test}
"mdx" {>= "1.10.0" & with-test} "kcas" {>= "0.3.0" & with-test}
"eio_luv" {= version} "yojson" {>= "2.0.2" & with-test}
"eio_linux"
{= version & os = "linux" &
(os-distribution != "centos" | os-version > "7")}
"eio_posix" {= version & os != "win32"}
"eio_windows" {= version & os = "win32"}
"odoc" {with-doc} "odoc" {with-doc}
] ]
build: [ build: [
["dune" "subst"] {dev}
[ [
"dune" "dune"
"build" "build"
@ -24,11 +28,10 @@ build: [
name name
"-j" "-j"
jobs jobs
"--promote-install-files=false"
"@install" "@install"
"@runtest" {with-test} "@runtest" {with-test}
"@doc" {with-doc} "@doc" {with-doc}
] ]
["dune" "install" "-p" name "--create-install-files" name]
] ]
dev-repo: "git+https://github.com/ocaml-multicore/eio.git" dev-repo: "git+https://github.com/ocaml-multicore/eio.git"
x-ci-accept-failures: ["macos-homebrew"]

1
eio_main.opam.template Normal file
View File

@ -0,0 +1 @@
x-ci-accept-failures: ["macos-homebrew"]

View File

@ -1,7 +1,7 @@
# This file is generated by dune, edit dune-project instead # This file is generated by dune, edit dune-project instead
opam-version: "2.0" opam-version: "2.0"
synopsis: "Eio implementation using luv (libuv)" synopsis: "Eio implementation for POSIX systems"
description: "An eio implementation for most platforms, using luv." description: "An Eio implementation for most Unix-like platforms"
maintainer: ["anil@recoil.org"] maintainer: ["anil@recoil.org"]
authors: ["Anil Madhavapeddy" "Thomas Leonard"] authors: ["Anil Madhavapeddy" "Thomas Leonard"]
license: "ISC" license: "ISC"
@ -9,18 +9,15 @@ homepage: "https://github.com/ocaml-multicore/eio"
doc: "https://ocaml-multicore.github.io/eio/" doc: "https://ocaml-multicore.github.io/eio/"
bug-reports: "https://github.com/ocaml-multicore/eio/issues" bug-reports: "https://github.com/ocaml-multicore/eio/issues"
depends: [ depends: [
"dune" {>= "2.9"} "dune" {>= "3.9"}
"base-domains"
"eio" {= version} "eio" {= version}
"luv" {>= "0.5.11"} "iomux" {>= "0.2"}
"luv_unix" {>= "0.5.0"} "mdx" {>= "2.4.1" & with-test}
"mdx" {>= "1.10.0" & with-test} "conf-bash" {with-test}
"logs" {>= "0.7.0"}
"fmt" {>= "0.8.9"} "fmt" {>= "0.8.9"}
"odoc" {with-doc} "odoc" {with-doc}
] ]
build: [ build: [
["dune" "subst"] {dev}
[ [
"dune" "dune"
"build" "build"
@ -28,11 +25,9 @@ build: [
name name
"-j" "-j"
jobs jobs
"--promote-install-files=false"
"@install" "@install"
"@runtest" {with-test} "@runtest" {with-test}
"@doc" {with-doc} "@doc" {with-doc}
] ]
["dune" "install" "-p" name "--create-install-files" name]
] ]
dev-repo: "git+https://github.com/ocaml-multicore/eio.git" dev-repo: "git+https://github.com/ocaml-multicore/eio.git"

33
eio_windows.opam Normal file
View File

@ -0,0 +1,33 @@
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
synopsis: "Eio implementation for Windows"
description: "An Eio implementation using OCaml's Unix.select"
maintainer: ["anil@recoil.org"]
authors: ["Anil Madhavapeddy" "Thomas Leonard"]
license: "ISC"
homepage: "https://github.com/ocaml-multicore/eio"
doc: "https://ocaml-multicore.github.io/eio/"
bug-reports: "https://github.com/ocaml-multicore/eio/issues"
depends: [
"dune" {>= "3.9"}
"eio" {= version}
"fmt" {>= "0.8.9"}
"kcas" {>= "0.3.0" & with-test}
"alcotest" {>= "1.7.0" & with-test}
"odoc" {with-doc}
]
build: [
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
]
dev-repo: "git+https://github.com/ocaml-multicore/eio.git"
#available: [os = "win32"]

View File

@ -0,0 +1 @@
#available: [os = "win32"]

3
examples/both/dune Normal file
View File

@ -0,0 +1,3 @@
(executable
(name main)
(libraries eio_main))

7
examples/both/main.ml Normal file
View File

@ -0,0 +1,7 @@
open Eio.Std
let () =
Eio_main.run @@ fun _env ->
Fiber.both
(fun () -> for x = 1 to 3 do traceln "x = %d" x; Fiber.yield () done)
(fun () -> for y = 1 to 3 do traceln "y = %d" y; Fiber.yield () done)

3
examples/capsicum/dune Normal file
View File

@ -0,0 +1,3 @@
(executable
(name main)
(libraries eio_main))

40
examples/capsicum/main.ml Normal file
View File

@ -0,0 +1,40 @@
open Eio.Std
let ( / ) = Eio.Path.( / )
let test_eio dir =
traceln "Using the file-system via the directory resource works:";
let test_file = dir / "capsicum-test.txt" in
traceln "Writing %a..." Eio.Path.pp test_file;
Eio.Path.save test_file "A test file" ~create:(`Exclusive 0o644);
traceln "Read: %S" (Eio.Path.load test_file);
Eio.Path.unlink test_file
let test_legacy () =
traceln "Bypassing Eio and accessing other resources should fail in Capsicum mode:";
let ch = open_in "/etc/passwd" in
let len = in_channel_length ch in
let data = really_input_string ch len in
close_in ch;
traceln "Was able to read /etc/passwd:@.%s" (String.trim data)
let () =
Eio_main.run @@ fun env ->
(* Parse command-line arguments *)
let path =
match Sys.argv with
| [| _; dir |] -> Eio.Stdenv.fs env / dir
| _ -> failwith "Usage: main.exe DIR"
in
if not (Eio.Path.is_directory path) then Fmt.failwith "%a is not a directory" Eio.Path.pp path;
(* Get access to resources before calling cap_enter: *)
Eio.Path.with_open_dir path @@ fun dir ->
traceln "Opened directory %a" Eio.Path.pp path;
(* Switch to capability mode, if possible: *)
begin match Eio_unix.Cap.enter () with
| Ok () -> traceln "Capsicum mode enabled"
| Error `Not_supported -> traceln "!! CAPSICUM PROTECTION NOT AVAILABLE !!"
end;
(* Run tests: *)
test_eio dir;
test_legacy ()

3
examples/fs/dune Normal file
View File

@ -0,0 +1,3 @@
(executable
(name main)
(libraries eio_main))

32
examples/fs/main.ml Normal file
View File

@ -0,0 +1,32 @@
(* Walk the directory tree rooted at the current directory,
showing a summary for any .mli files. *)
let ( / ) = Eio.Path.( / )
let is_doc_comment = String.starts_with ~prefix:"(** "
(* Print the first line of [t]'s doc-comment, if any *)
let scan_mli t f =
Eio.Path.with_lines t (fun lines ->
Seq.find is_doc_comment lines
|> Option.iter (fun line ->
let stop = String.index_from_opt line 4 '*' |> Option.value ~default:(String.length line) in
Format.fprintf f "%a: %s@." Eio.Path.pp t (String.sub line 4 (stop - 4))
)
)
(* Walk the tree rooted at [t] and scan any .mli files found. *)
let rec scan t f =
match Eio.Path.kind ~follow:false t with
| `Directory ->
Eio.Path.read_dir t |> List.iter (function
| "_build" | "_opam" -> () (* Don't examine these directories *)
| item when String.starts_with ~prefix:"." item -> () (* Skip hidden items *)
| item -> scan (t / item) f
)
| `Regular_file when Filename.check_suffix (snd t) ".mli" -> scan_mli t f
| _ -> ()
let () =
Eio_main.run @@ fun env ->
scan (Eio.Stdenv.cwd env) Format.std_formatter

3
examples/hello/dune Normal file
View File

@ -0,0 +1,3 @@
(executable
(name main)
(libraries eio_main))

6
examples/hello/main.ml Normal file
View File

@ -0,0 +1,6 @@
let main ~stdout =
Eio.Flow.copy_string "Hello, world!\n" stdout
let () =
Eio_main.run @@ fun env ->
main ~stdout:(Eio.Stdenv.stdout env)

21
examples/net/client.ml Normal file
View File

@ -0,0 +1,21 @@
open Eio.Std
(* Prefix all trace output with "client: " *)
let traceln fmt = traceln ("client: " ^^ fmt)
module Read = Eio.Buf_read
module Write = Eio.Buf_write
(* Connect to [addr] on [net], send a message and then read the reply. *)
let run ~net ~addr =
Switch.run ~name:"client" @@ fun sw ->
traceln "Connecting to server at %a..." Eio.Net.Sockaddr.pp addr;
let flow = Eio.Net.connect ~sw net addr in
(* We use a buffered writer here so we can create the message in multiple
steps but still send it efficiently as a single packet: *)
Write.with_flow flow @@ fun to_server ->
Write.string to_server "Hello";
Write.char to_server ' ';
Write.string to_server "from client\n";
let reply = Read.(parse_exn take_all) flow ~max_size:100 in
traceln "Got reply %S" reply

3
examples/net/dune Normal file
View File

@ -0,0 +1,3 @@
(executable
(name main)
(libraries eio_main))

20
examples/net/main.ml Normal file
View File

@ -0,0 +1,20 @@
open Eio.Std
let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 8080)
(* Run a server and a test client, communicating using [net]. *)
let main ~net =
Switch.run ~name:"main" @@ fun sw ->
(* We create the listening socket first so that we can be sure it is ready
as soon as the client wants to use it. *)
let listening_socket = Eio.Net.listen net ~sw ~reuse_addr:true ~backlog:5 addr in
(* Start the server running in a new fiber.
Using [fork_daemon] here means that it will be stopped once the client is done
(we don't wait for it to finish because it will keep accepting new connections forever). *)
Fiber.fork_daemon ~sw (fun () -> Server.run listening_socket);
(* Test the server: *)
Client.run ~net ~addr
let () =
Eio_main.run @@ fun env ->
main ~net:(Eio.Stdenv.net env)

24
examples/net/server.ml Normal file
View File

@ -0,0 +1,24 @@
open Eio.Std
(* Prefix all trace output with "server: " *)
let traceln fmt = traceln ("server: " ^^ fmt)
module Read = Eio.Buf_read
(* Read one line from [client] and respond with "OK". *)
let handle_client flow addr =
traceln "Accepted connection from %a" Eio.Net.Sockaddr.pp addr;
(* We use a buffered reader because we may need to combine multiple reads
to get a single line (or we may get multiple lines in a single read,
although here we only use the first one). *)
let from_client = Read.of_flow flow ~max_size:100 in
traceln "Received: %S" (Read.line from_client);
Eio.Flow.copy_string "OK" flow
(* Accept incoming client connections on [socket].
We can handle multiple clients at the same time.
Never returns (but can be cancelled). *)
let run socket =
Eio.Net.run_server socket handle_client
~on_error:(traceln "Error handling connection: %a" Fmt.exn)
~max_connections:1000

3
examples/signals/dune Normal file
View File

@ -0,0 +1,3 @@
(executable
(name main)
(libraries eio_main))

28
examples/signals/main.ml Normal file
View File

@ -0,0 +1,28 @@
open Eio.Std
let load_config () =
(* A real system would load the file and then pass it to the running service
somehow, but we're just demonstrating signal handling so just sleep to
simulate some time taken to load the new configuration. *)
Eio_unix.sleep 2.0
(* $MDX part-begin=main *)
let main ~config_changed =
Eio.Condition.loop_no_mutex config_changed (fun () ->
traceln "Reading configuration ('kill -SIGHUP %d' to reload)..." (Unix.getpid ());
load_config ();
traceln "Finished reading configuration";
None (* Keep waiting for futher changes *)
)
(* $MDX part-end *)
let () =
Eio_main.run @@ fun _env ->
let config_changed = Eio.Condition.create () in
let handle_signal (_signum : int) =
(* Warning: we're in a signal handler now.
Most operations are unsafe here, except for Eio.Condition.broadcast! *)
Eio.Condition.broadcast config_changed
in
Sys.set_signal Sys.sighup (Signal_handle handle_signal);
main ~config_changed

3
examples/trace/dune Normal file
View File

@ -0,0 +1,3 @@
(executable
(name main)
(libraries eio.runtime_events eio_main))

69
examples/trace/main.ml Normal file
View File

@ -0,0 +1,69 @@
(* This example shows how to trace an Eio program.
The [main] function creates a listening socket and has a client connect and send a message,
which is handled by a server fiber.
At the same time, another fiber is displaying trace events.
For simplicity, this example runs the tracer in the same process as the program being traced,
but typically they would be separate processes. *)
open Eio.Std
let callbacks =
Runtime_events.Callbacks.create ()
(* Uncomment to trace GC events too: *)
(*
~runtime_begin:(handle (fun f phase -> Fmt.pf f "begin %s" (Runtime_events.runtime_phase_name phase)))
~runtime_end:(handle (fun f phase -> Fmt.pf f "end %s" (Runtime_events.runtime_phase_name phase)))
*)
~lost_events:(fun ring n -> traceln "ring %d lost %d events" ring n)
|> Eio_runtime_events.add_callbacks
(fun ring ts e ->
(* Note: don't use traceln here, as it will just generate more log events! *)
Fmt.epr "%9Ld:ring %d: %a@." (Runtime_events.Timestamp.to_int64 ts) ring Eio_runtime_events.pp_event e
)
(* (see lib_eio/runtime_events/eio_runtime_events.mli for more event types) *)
(* Read and display trace events from [cursor] until [finished]. *)
let trace ~finished (clock, delay) cursor =
traceln "tracer: starting";
let rec aux () =
let _ : int = Runtime_events.read_poll cursor callbacks None in
if !finished then (
traceln "tracer: stopping"
) else (
Eio.Time.Mono.sleep clock delay;
aux ()
)
in
aux ()
(* The program to be traced. *)
let main net =
Switch.run ~name:"main" @@ fun sw ->
let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 8123) in
let s = Eio.Net.listen ~sw ~backlog:1 ~reuse_addr:true net addr in
Fiber.both
(fun () ->
traceln "server: starting";
let c, _addr = Eio.Net.accept ~sw s in
traceln "server: got connection from client";
let msg = Eio.Flow.read_all c in
traceln "server: read %S from socket" msg
)
(fun () ->
traceln "client: connecting socket...";
let c = Eio.Net.connect ~sw net addr in
Eio.Flow.copy_string "Hello" c;
Eio.Flow.close c
)
(* Enable tracing then run the [main] and [trace] fibers. *)
let () =
Runtime_events.start ();
let cursor = Runtime_events.create_cursor None in (* Create a in-process cursor *)
Eio_main.run @@ fun env ->
let finished = ref false in
Fiber.both
(fun () -> trace ~finished (env#mono_clock, 0.01) cursor)
(fun () -> main env#net; finished := true)

View File

@ -1,4 +1,4 @@
(test (tests
(package eio) (package eio)
(libraries cstruct crowbar fmt astring eio) (libraries cstruct crowbar fmt eio eio.mock eio.unix)
(name test)) (names fuzz_buf_read fuzz_buf_write fuzz_inherit_fds))

View File

@ -2,7 +2,21 @@
It runs random operations on both the model and the real buffer and It runs random operations on both the model and the real buffer and
checks they always give the same result. *) checks they always give the same result. *)
open Astring module String = struct
include String
let rec find ?(start=0) p t =
if start = String.length t then None
else if p t.[start] then Some start
else find ~start:(succ start) p t
let drop t n = String.sub t n (String.length t - n)
let cut ~sep t =
match String.index_opt t sep with
| None -> None
| Some i -> Some (String.sub t 0 i, drop t (i + 1))
end
let debug = false let debug = false
@ -12,30 +26,34 @@ exception Buffer_limit_exceeded = Buf_read.Buffer_limit_exceeded
let initial_size = 10 let initial_size = 10
let max_size = 100 let max_size = 100
let mock_flow next = object (self) module Mock_flow = struct
inherit Eio.Flow.source type t = string list ref
val mutable next = next let rec single_read t buf =
match !t with
method read_into buf =
match next with
| [] -> | [] ->
raise End_of_file raise End_of_file
| "" :: xs -> | "" :: xs ->
next <- xs; t := xs;
self#read_into buf single_read t buf
| x :: xs -> | x :: xs ->
let len = min (Cstruct.length buf) (String.length x) in let len = min (Cstruct.length buf) (String.length x) in
Cstruct.blit_from_string x 0 buf 0 len; Cstruct.blit_from_string x 0 buf 0 len;
let x' = String.with_index_range x ~first:len in let x' = String.drop x len in
next <- (if x' = "" then xs else x' :: xs); t := (if x' = "" then xs else x' :: xs);
len len
let read_methods = []
end end
let mock_flow =
let ops = Eio.Flow.Pi.source (module Mock_flow) in
fun chunks -> Eio.Resource.T (ref chunks, ops)
module Model = struct module Model = struct
type t = string ref type t = string ref
let of_chunks chunks = ref (String.concat chunks) let of_chunks chunks = ref (String.concat "" chunks)
let take_all t = let take_all t =
let old = !t in let old = !t in
@ -44,11 +62,11 @@ module Model = struct
old old
let line t = let line t =
match String.cut ~sep:"\n" !t with match String.cut ~sep:'\n' !t with
| Some (line, rest) -> | Some (line, rest) ->
if String.length line >= max_size then raise Buffer_limit_exceeded; if String.length line >= max_size then raise Buffer_limit_exceeded;
t := rest; t := rest;
if String.is_suffix ~affix:"\r" line then String.with_index_range line ~last:(String.length line - 2) if String.ends_with ~suffix:"\r" line then String.sub line 0 (String.length line - 1)
else line else line
| None when !t = "" -> raise End_of_file | None when !t = "" -> raise End_of_file
| None when String.length !t >= max_size -> raise Buffer_limit_exceeded | None when String.length !t >= max_size -> raise Buffer_limit_exceeded
@ -58,13 +76,16 @@ module Model = struct
match !t with match !t with
| "" -> raise End_of_file | "" -> raise End_of_file
| s -> | s ->
t := String.with_index_range s ~first:1; t := String.drop s 1;
String.get_head s s.[0]
let peek_char t = String.head !t let peek_char t =
match !t with
| "" -> None
| s -> Some (s.[0])
let consume t n = let consume t n =
t := String.with_index_range !t ~first:n t := String.drop !t n
let char c t = let char c t =
match peek_char t with match peek_char t with
@ -75,17 +96,18 @@ module Model = struct
let string s t = let string s t =
if debug then Fmt.pr "string %S@." s; if debug then Fmt.pr "string %S@." s;
let len_t = String.length !t in let len_t = String.length !t in
if not (String.is_prefix ~affix:(String.with_range s ~len:len_t) !t) then failwith "string"; let prefix = String.sub s 0 (min len_t (String.length s)) in
if not (String.starts_with ~prefix !t) then failwith "string";
if String.length s > max_size then raise Buffer_limit_exceeded; if String.length s > max_size then raise Buffer_limit_exceeded;
if String.is_prefix ~affix:s !t then consume t (String.length s) if String.starts_with ~prefix:s !t then consume t (String.length s)
else raise End_of_file else raise End_of_file
let take n t = let take n t =
if n < 0 then invalid_arg "neg"; if n < 0 then invalid_arg "neg";
if n > max_size then raise Buffer_limit_exceeded if n > max_size then raise Buffer_limit_exceeded
else if String.length !t >= n then ( else if String.length !t >= n then (
let data = String.with_range !t ~len:n in let data = String.sub !t 0 n in
t := String.with_range !t ~first:n; t := String.drop !t n;
data data
) else raise End_of_file ) else raise End_of_file
@ -93,7 +115,7 @@ module Model = struct
match String.find (Fun.negate p) !t with match String.find (Fun.negate p) !t with
| Some i when i >= max_size -> raise Buffer_limit_exceeded | Some i when i >= max_size -> raise Buffer_limit_exceeded
| Some i -> | Some i ->
let data = String.with_range !t ~len:i in let data = String.sub !t 0 i in
consume t i; consume t i;
data data
| None -> take_all t | None -> take_all t
@ -118,6 +140,64 @@ module Model = struct
match line t with match line t with
| line -> line :: lines t | line -> line :: lines t
| exception End_of_file -> [] | exception End_of_file -> []
module BE = struct
let uint16 t = String.get_uint16_be (take 2 t) 0
let uint32 t = String.get_int32_be (take 4 t) 0
let uint48 t =
let s = take 6 t in
let upper_16 = String.get_uint16_be s 0 |> Int64.of_int in
let middle_16 = String.get_uint16_be s 2 |> Int64.of_int in
let lower_16 = String.get_uint16_be s 4 |> Int64.of_int in
Int64.(
add
(shift_left upper_16 32)
(add
(shift_left middle_16 16)
(lower_16))
)
let uint64 t = String.get_int64_be (take 8 t) 0
let float t =
Int32.float_of_bits (
String.get_int32_be (take 4 t) 0)
let double t =
Int64.float_of_bits (
String.get_int64_be (take 8 t) 0)
end
module LE = struct
let uint16 t = String.get_uint16_le (take 2 t) 0
let uint32 t = String.get_int32_le (take 4 t) 0
let uint48 t =
let s = take 6 t in
let lower_16 = String.get_uint16_le s 0 |> Int64.of_int in
let middle_16 = String.get_uint16_le s 2 |> Int64.of_int in
let upper_16 = String.get_uint16_le s 4 |> Int64.of_int in
Int64.(
add
(shift_left upper_16 32)
(add
(shift_left middle_16 16)
(lower_16))
)
let uint64 t = String.get_int64_le (take 8 t) 0
let float t =
Int32.float_of_bits (
String.get_int32_le (take 4 t) 0)
let double t =
Int64.float_of_bits (
String.get_int64_le (take 8 t) 0)
end
end end
type op = Op : 'a Crowbar.printer * 'a Buf_read.parser * (Model.t -> 'a) -> op type op = Op : 'a Crowbar.printer * 'a Buf_read.parser * (Model.t -> 'a) -> op
@ -144,6 +224,18 @@ let op =
"skip", Crowbar.(map [int]) (fun n -> Op (unit, Buf_read.skip n, Model.skip n)); "skip", Crowbar.(map [int]) (fun n -> Op (unit, Buf_read.skip n, Model.skip n));
"end_of_input", Crowbar.const @@ Op (unit, Buf_read.end_of_input, Model.end_of_input); "end_of_input", Crowbar.const @@ Op (unit, Buf_read.end_of_input, Model.end_of_input);
"lines", Crowbar.const @@ Op (Fmt.Dump.(list string), (Buf_read.(map List.of_seq lines)), Model.lines); "lines", Crowbar.const @@ Op (Fmt.Dump.(list string), (Buf_read.(map List.of_seq lines)), Model.lines);
"be_uint16", Crowbar.const @@ Op (Fmt.int, (Buf_read.BE.uint16), Model.BE.uint16);
"be_uint32", Crowbar.const @@ Op (Fmt.int32, (Buf_read.BE.uint32), Model.BE.uint32);
"be_uint48", Crowbar.const @@ Op (Fmt.int64, (Buf_read.BE.uint48), Model.BE.uint48);
"be_uint64", Crowbar.const @@ Op (Fmt.int64, (Buf_read.BE.uint64), Model.BE.uint64);
"be_float", Crowbar.const @@ Op (Fmt.float, (Buf_read.BE.float), Model.BE.float);
"be_double", Crowbar.const @@ Op (Fmt.float, (Buf_read.BE.double), Model.BE.double);
"le_uint16", Crowbar.const @@ Op (Fmt.int, (Buf_read.LE.uint16), Model.LE.uint16);
"le_uint32", Crowbar.const @@ Op (Fmt.int32, (Buf_read.LE.uint32), Model.LE.uint32);
"le_uint48", Crowbar.const @@ Op (Fmt.int64, (Buf_read.LE.uint48), Model.LE.uint48);
"le_uint64", Crowbar.const @@ Op (Fmt.int64, (Buf_read.LE.uint64), Model.LE.uint64);
"le_float", Crowbar.const @@ Op (Fmt.float, (Buf_read.LE.float), Model.LE.float);
"le_double", Crowbar.const @@ Op (Fmt.float, (Buf_read.LE.double), Model.LE.double);
] ]
let catch f x = let catch f x =

49
fuzz/fuzz_buf_write.ml Normal file
View File

@ -0,0 +1,49 @@
(* Run a random sequence of write operations on an [Eio.Buf_write].
Check that the expected data gets written to the flow. *)
module W = Eio.Buf_write
let initial_size = 10
type op = Op : string * (W.t -> unit) -> op (* Expected string, writer *)
let cstruct =
Crowbar.(map [bytes; int; int]) (fun s off len ->
if String.length s = 0 then Cstruct.empty
else (
let off = min (abs off) (String.length s) in
let len = min (abs len) (String.length s - off) in
Cstruct.of_string s ~off ~len
)
)
let op =
let label (name, gen) = Crowbar.with_printer (fun f (Op (s, _)) -> Fmt.pf f "%s:%S" name s) gen in
Crowbar.choose @@ List.map label [
"string", Crowbar.(map [bytes]) (fun s -> Op (s, (fun t -> W.string t s)));
"cstruct", Crowbar.(map [cstruct]) (fun cs -> Op (Cstruct.to_string cs, (fun t -> W.cstruct t cs)));
"schedule_cstruct", Crowbar.(map [cstruct]) (fun cs -> Op (Cstruct.to_string cs, (fun t -> W.schedule_cstruct t cs)));
"yield", Crowbar.const @@ Op ("", (fun _ -> Eio.Fiber.yield ()));
"flush", Crowbar.const @@ Op ("", W.flush);
"pause", Crowbar.const @@ Op ("", W.pause);
"unpause", Crowbar.const @@ Op ("", W.unpause);
]
let random ops close =
Eio_mock.Backend.run @@ fun _ ->
let b = Buffer.create 100 in
let flow = Eio.Flow.buffer_sink b in
let expected = ref [] in
W.with_flow flow ~initial_size (fun t ->
let perform (Op (s, write)) =
expected := s :: !expected;
write t
in
List.iter perform ops;
if close then W.close t
);
let expected = String.concat "" (List.rev !expected) in
Crowbar.check_eq ~pp:Fmt.Dump.string (Buffer.contents b) expected
let () =
Crowbar.(add_test ~name:"random ops" [list op; bool] random)

46
fuzz/fuzz_inherit_fds.ml Normal file
View File

@ -0,0 +1,46 @@
module I = Eio_unix__Inherit_fds
module S = Set.Make(Int)
let pp f = function
| `Cloexec x -> Fmt.pf f "close %d" x
| `Keep x -> Fmt.pf f "keep %d" x
let rec has_duplicates ~seen = function
| [] -> false
| (dst, _) :: _ when S.mem dst seen -> true
| (dst, _) :: xs -> has_duplicates xs ~seen:(S.add dst seen)
let inherit_fds mapping =
let has_duplicates = has_duplicates ~seen:S.empty mapping in
let fds = Hashtbl.create 10 in
mapping |> List.iter (fun (_dst, src) ->
Hashtbl.add fds src (`Cloexec src);
);
match I.plan mapping with
| exception (Invalid_argument _) -> assert has_duplicates
| plan ->
assert (not has_duplicates);
plan |> List.iter (fun {I.src; dst} ->
(* Fmt.pr "%d -> %d@." src dst; *)
let v =
match Hashtbl.find fds src with
| `Cloexec x | `Keep x ->
if dst = -1 then `Cloexec x else `Keep x
in
Hashtbl.add fds dst v
);
mapping |> List.iter (fun (dst, src) ->
let v = Hashtbl.find fds dst in
Crowbar.check_eq ~pp v (`Keep src);
Hashtbl.remove fds dst;
);
fds |> Hashtbl.iter (fun x -> function
| `Cloexec _ -> ()
| `Keep _ -> Fmt.failwith "%d should be close-on-exec!" x
)
let fd = Crowbar.range 10 (* Restrict range to make cycles more likely *)
let () =
Crowbar.(add_test ~name:"inherit_fds" [list (pair fd fd)] inherit_fds)

View File

@ -1,16 +1,20 @@
exception Buffer_limit_exceeded exception Buffer_limit_exceeded
open Std
type t = { type t = {
mutable buf : Cstruct.buffer; mutable buf : Cstruct.buffer;
mutable pos : int; mutable pos : int;
mutable len : int; mutable len : int;
mutable flow : Flow.source option; (* None if we've seen eof *) mutable flow : Flow.source_ty r option; (* None if we've seen eof *)
mutable consumed : int; (* Total bytes consumed so far *) mutable consumed : int; (* Total bytes consumed so far *)
max_size : int; max_size : int;
} }
type 'a parser = t -> 'a type 'a parser = t -> 'a
let return = Fun.const
let map f x r = f (x r) let map f x r = f (x r)
let pair x y r = let pair x y r =
@ -26,6 +30,8 @@ module Syntax = struct
let ( and* ) = pair let ( and* ) = pair
let ( and+ ) = pair let ( and+ ) = pair
let ( <*> ) = pair
let ( <* ) a b t = let ( <* ) a b t =
let x = a t in let x = a t in
ignore (b t); ignore (b t);
@ -41,17 +47,30 @@ open Syntax
let capacity t = Bigarray.Array1.dim t.buf let capacity t = Bigarray.Array1.dim t.buf
let of_flow ?initial_size ~max_size flow = let of_flow ?initial_size ~max_size flow =
let flow = (flow :> Flow.source) in let flow = (flow :> Flow.source_ty r) in
if max_size <= 0 then Fmt.invalid_arg "Max size %d should be positive!" max_size; if max_size <= 0 then Fmt.invalid_arg "Max size %d should be positive!" max_size;
let initial_size = Option.value initial_size ~default:(min 4096 max_size) in let initial_size = Option.value initial_size ~default:(min 4096 max_size) in
let buf = Bigarray.(Array1.create char c_layout initial_size) in let buf = Bigarray.(Array1.create char c_layout initial_size) in
{ buf; pos = 0; len = 0; flow = Some flow; max_size; consumed = 0 } { buf; pos = 0; len = 0; flow = Some flow; max_size; consumed = 0 }
let of_buffer buf =
let len = Bigarray.Array1.dim buf in
{ buf; pos = 0; len; flow = None; max_size = max_int; consumed = 0 }
let of_string s =
let len = String.length s in
let buf = Bigarray.(Array1.create char c_layout) len in
Cstruct.blit_from_string s 0 (Cstruct.of_bigarray buf) 0 len;
of_buffer buf
let peek t = let peek t =
Cstruct.of_bigarray ~off:t.pos ~len:t.len t.buf Cstruct.of_bigarray ~off:t.pos ~len:t.len t.buf
let consume t n = let consume_err t n =
if n < 0 || n > t.len then Fmt.invalid_arg "Can't consume %d bytes of a %d byte buffer!" n t.len; Fmt.invalid_arg "Can't consume %d bytes of a %d byte buffer!" n t.len
let [@inline] consume t n =
if n < 0 || n > t.len then consume_err t n;
t.pos <- t.pos + n; t.pos <- t.pos + n;
t.len <- t.len - n; t.len <- t.len - n;
t.consumed <- t.consumed + n t.consumed <- t.consumed + n
@ -66,9 +85,8 @@ let consumed_bytes t = t.consumed
let eof_seen t = t.flow = None let eof_seen t = t.flow = None
let ensure t n = let ensure_slow_path t n =
assert (n >= 0); assert (n >= 0);
if t.len < n then (
if n > t.max_size then raise Buffer_limit_exceeded; if n > t.max_size then raise Buffer_limit_exceeded;
(* We don't have enough data yet, so we'll need to do a read. *) (* We don't have enough data yet, so we'll need to do a read. *)
match t.flow with match t.flow with
@ -101,30 +119,129 @@ let ensure t n =
while t.len < n do while t.len < n do
let free_space = Cstruct.of_bigarray t.buf ~off:(t.pos + t.len) in let free_space = Cstruct.of_bigarray t.buf ~off:(t.pos + t.len) in
assert (t.len + Cstruct.length free_space >= n); assert (t.len + Cstruct.length free_space >= n);
let got = Flow.read flow free_space in let got = Flow.single_read flow free_space in
t.len <- t.len + got t.len <- t.len + got
done done;
assert (buffered_bytes t >= n)
with End_of_file -> with End_of_file ->
t.flow <- None; t.flow <- None;
raise End_of_file raise End_of_file
);
assert (buffered_bytes t >= n)
let as_flow t = let ensure t n =
object if t.len < n then ensure_slow_path t n
inherit Flow.source
method read_into dst = module F = struct
type nonrec t = t
let single_read t dst =
ensure t 1; ensure t 1;
let len = min (buffered_bytes t) (Cstruct.length dst) in let len = min (buffered_bytes t) (Cstruct.length dst) in
Cstruct.blit (peek t) 0 dst 0 len; Cstruct.blit (peek t) 0 dst 0 len;
consume t len; consume t len;
len len
end
let rsb t fn =
ensure t 1;
let data = peek t in
let sent = fn [data] in
consume t sent
let read_methods = [Flow.Read_source_buffer rsb]
end
let as_flow =
let ops = Flow.Pi.source (module F) in
fun t -> Resource.T (t, ops)
let get t i = let get t i =
Bigarray.Array1.get t.buf (t.pos + i) Bigarray.Array1.get t.buf (t.pos + i)
module BE = struct
let uint16 t =
ensure t 2;
let data = Bigstringaf.get_int16_be t.buf t.pos in
consume t 2;
data
let uint32 t =
ensure t 4;
let data = Bigstringaf.get_int32_be t.buf t.pos in
consume t 4;
data
let uint48 t =
ensure t 6;
let upper_32 = Bigstringaf.get_int32_be t.buf t.pos |> Int64.of_int32 |> Int64.logand 0xffffffffL in
let lower_16 = Bigstringaf.get_int16_be t.buf (t.pos + 4) |> Int64.of_int in
consume t 6;
Int64.(
logor
(lower_16)
(shift_left upper_32 16)
)
let uint64 t =
ensure t 8;
let data = Bigstringaf.get_int64_be t.buf t.pos in
consume t 8;
data
let float t =
ensure t 4;
let data = Bigstringaf.unsafe_get_int32_be t.buf t.pos in
consume t 4;
Int32.float_of_bits data
let double t =
ensure t 8;
let data = Bigstringaf.unsafe_get_int64_be t.buf t.pos in
consume t 8;
Int64.float_of_bits data
end
module LE = struct
let uint16 t =
ensure t 2;
let data = Bigstringaf.get_int16_le t.buf t.pos in
consume t 2;
data
let uint32 t =
ensure t 4;
let data = Bigstringaf.get_int32_le t.buf t.pos in
consume t 4;
data
let uint48 t =
ensure t 6;
let lower_32 = Bigstringaf.get_int32_le t.buf t.pos |> Int64.of_int32 |> Int64.logand 0xffffffffL in
let upper_16 = Bigstringaf.get_int16_le t.buf (t.pos + 4) |> Int64.of_int in
consume t 6;
Int64.(
logor
(shift_left upper_16 32)
lower_32
)
let uint64 t =
ensure t 8;
let data = Bigstringaf.get_int64_le t.buf t.pos in
consume t 8;
data
let float t =
ensure t 4;
let data = Bigstringaf.unsafe_get_int32_le t.buf t.pos in
consume t 4;
Int32.float_of_bits data
let double t =
ensure t 8;
let data = Bigstringaf.unsafe_get_int64_le t.buf t.pos in
consume t 8;
Int64.float_of_bits data
end
let char c t = let char c t =
ensure t 1; ensure t 1;
let c2 = get t 0 in let c2 = get t 0 in
@ -137,6 +254,8 @@ let any_char t =
consume t 1; consume t 1;
c c
let uint8 t = Char.code (any_char t)
let peek_char t = let peek_char t =
match ensure t 1 with match ensure t 1 with
| () -> Some (get t 0) | () -> Some (get t 0)
@ -197,6 +316,14 @@ let take_while p t =
consume t len; consume t len;
data data
let take_while1 p t =
let len = count_while p t in
if len < 1 then Fmt.failwith "take_while1"
else
let data = Cstruct.to_string (Cstruct.of_bigarray t.buf ~off:t.pos ~len) in
consume t len;
data
let skip_while p t = let skip_while p t =
let rec aux i = let rec aux i =
if i < t.len then ( if i < t.len then (
@ -211,6 +338,11 @@ let skip_while p t =
try aux 0 try aux 0
with End_of_file -> () with End_of_file -> ()
let skip_while1 p t =
let len = count_while p t in
if len < 1 then Fmt.failwith "skip_while1"
else consume t len
let rec skip n t = let rec skip n t =
if n <= t.len then ( if n <= t.len then (
consume t n consume t n
@ -283,6 +415,14 @@ let parse_exn ?initial_size ~max_size p flow =
| Ok x -> x | Ok x -> x
| Error (`Msg m) -> failwith m | Error (`Msg m) -> failwith m
let parse_string p s =
format_errors (p <* end_of_input) (of_string s)
let parse_string_exn p s =
match parse_string p s with
| Ok x -> x
| Error (`Msg m) -> failwith m
[@@inline never] [@@inline never]
let bad_offset ~expected actual = let bad_offset ~expected actual =
Fmt.invalid_arg "Sequence is stale (expected to be used at offset %d, but stream is now at %d)" Fmt.invalid_arg "Sequence is stale (expected to be used at offset %d, but stream is now at %d)"

325
lib_eio/buf_read.mli Normal file
View File

@ -0,0 +1,325 @@
(** This module provides fairly efficient non-backtracking parsers.
It is modelled on Angstrom's API, and you should use that if
backtracking is needed.
Example:
{[
let r = Buf_read.of_flow flow ~max_size:1_000_000 in
Buf_read.line r
]}
*)
open Std
type t
(** An input buffer. *)
exception Buffer_limit_exceeded
(** Raised if parsing an item would require enlarging the buffer beyond its configured limit. *)
type 'a parser = t -> 'a
(** An ['a parser] is a function that consumes and returns a value of type ['a].
@raise Failure The flow can't be parsed as a value of type ['a].
@raise End_of_file The flow ended without enough data to parse an ['a].
@raise Buffer_limit_exceeded Parsing the value would exceed the configured size limit. *)
val parse : ?initial_size:int -> max_size:int -> 'a parser -> _ Flow.source -> ('a, [> `Msg of string]) result
(** [parse p flow ~max_size] uses [p] to parse everything in [flow].
It is a convenience function that does
{[
let buf = of_flow flow ~max_size in
format_errors (p <* end_of_input) buf
]}
@param initial_size see {!of_flow}. *)
val parse_exn : ?initial_size:int -> max_size:int -> 'a parser -> _ Flow.source -> 'a
(** [parse_exn] wraps {!parse}, but raises [Failure msg] if that returns [Error (`Msg msg)].
Catching exceptions with [parse] and then raising them might seem pointless,
but this has the effect of turning e.g. an [End_of_file] exception into a [Failure]
with a more user-friendly message. *)
val parse_string : 'a parser -> string -> ('a, [> `Msg of string]) result
(** [parse_string p s] uses [p] to parse everything in [s].
It is defined as [format_errors (p <* end_of_input) (of_string s)] *)
val parse_string_exn : 'a parser -> string -> 'a
(** [parse_string_exn] is like {!parse_string}, but handles errors like {!parse_exn}. *)
val of_flow : ?initial_size:int -> max_size:int -> _ Flow.source -> t
(** [of_flow ~max_size flow] is a buffered reader backed by [flow].
@param initial_size The initial amount of memory to allocate for the buffer.
@param max_size The maximum size to which the buffer may grow.
This must be large enough to hold the largest single item
you want to parse (e.g. the longest line, if using
{!line}), plus any terminator needed to know the value is
complete (e.g. the newline character(s)). This is just to
prevent a run-away input from consuming all memory, and
you can usually just set it much larger than you expect
to need. *)
val of_buffer : Cstruct.buffer -> t
(** [of_buffer buf] is a reader that reads from [buf].
[buf] is used directly, without being copied.
[eof_seen (of_buffer buf) = true].
This module will not modify [buf] itself, but it will expose it via {!peek}. *)
val of_string : string -> t
(** [of_string s] is a reader that reads from [s]. *)
val as_flow : t -> Flow.source_ty r
(** [as_flow t] is a buffered flow.
Reading from it will return data from the buffer,
only reading the underlying flow if the buffer is empty. *)
(** {2 Reading data} *)
val line : string parser
(** [line] parses one line.
Lines can be terminated by either LF or CRLF.
The returned string does not include the terminator.
If [End_of_file] is reached after seeing some data but before seeing a line
terminator, the data seen is returned as the last line. *)
val lines : string Seq.t parser
(** [lines] returns a sequence that lazily reads the next line until the end of the input is reached.
[lines = seq line ~stop:at_end_of_input] *)
val char : char -> unit parser
(** [char c] checks that the next byte is [c] and consumes it.
@raise Failure if the next byte is not [c] *)
val any_char : char parser
(** [any_char] parses one character. *)
val peek_char : char option parser
(** [peek_char] returns [Some c] where [c] is the next character, but does not consume it.
Returns [None] at the end of the input stream rather than raising [End_of_file]. *)
val string : string -> unit parser
(** [string s] checks that [s] is the next string in the stream and consumes it.
@raise Failure if [s] is not a prefix of the stream. *)
val uint8 : int parser
(** [uint8] parses the next byte as an unsigned 8-bit integer. *)
(** Big endian parsers *)
module BE : sig
val uint16 : int parser
(** [uint16] parses the next 2 bytes as the lower 16 bits of an [int] in big-endian byte order *)
val uint32 : int32 parser
(** [uint32] parses the next 4 bytes as an [int32] in big-endian byte order *)
val uint48 : int64 parser
(** [uint48] parses the next 6 bytes as a 48-bit unsigned big-endian integer *)
val uint64 : int64 parser
(** [uint64] parses the next 8 bytes as an [int64] in big-endian byte order *)
val float : float parser
(** [float] parses the next 4 bytes as a [float] in big-endian byte order *)
val double : float parser
(** [double] parses the next 8 bytes as a [float] in big-endian byte order *)
end
(** Little endian parsers *)
module LE : sig
val uint16 : int parser
(** [uint16] parses the next 2 bytes as the lower 16 bits of an [int] in little-endian byte order *)
val uint32 : int32 parser
(** [uint32] parses the next 4 bytes as an [int32] in little-endian byte order *)
val uint48 : int64 parser
(** [uint48] parses the next 6 bytes as a 48-bit unsigned big-endian integer *)
val uint64 : int64 parser
(** [uint64] parses the next 8 bytes as an [int64] in little-endian byte order *)
val float : float parser
(** [float] parses the next 4 bytes as a [float] in little-endian byte order *)
val double : float parser
(** [double] parses the next 8 bytes as a [float] in little-endian byte order *)
end
val take : int -> string parser
(** [take n] takes exactly [n] bytes from the input. *)
val take_all : string parser
(** [take_all] takes all remaining data until end-of-file.
Returns [""] if already at end-of-file.
@raise Buffer_limit_exceeded if the remaining data exceeds or equals the buffer limit
(it needs one extra byte to confirm it has reached end-of-file). *)
val take_while : (char -> bool) -> string parser
(** [take_while p] finds the first byte for which [p] is false
and consumes and returns all bytes before that.
If [p] is true for all remaining bytes, it returns everything until end-of-file.
It will return the empty string if there are no matching characters
(and therefore never raises [End_of_file]). *)
val take_while1 : (char -> bool) -> string parser
(** [take_while1 p] is like [take_while]. However, the parser fails with "take_while1"
if at least one character of input hasn't been consumed by the parser. *)
val skip_while : (char -> bool) -> unit parser
(** [skip_while p] skips zero or more bytes for which [p] is [true].
[skip_while p t] does the same thing as [ignore (take_while p t)],
except that it is not limited by the buffer size. *)
val skip_while1 : (char -> bool) -> unit parser
(** [skip_while1 p] is like [skip_while]. However, the parser fails with "skip_while1" if
at least one character of input hasn't been skipped. *)
val skip : int -> unit parser
(** [skip n] discards the next [n] bytes.
[skip n] = [map ignore (take n)],
except that the number of skipped bytes may be larger than the buffer (it will not grow).
Note: if [End_of_file] is raised, all bytes in the stream will have been consumed. *)
val at_end_of_input : bool parser
(** [at_end_of_input] returns [true] when at the end of the stream, or
[false] if there is at least one more byte to be read. *)
val end_of_input : unit parser
(** [end_of_input] checks that there are no further bytes in the stream.
@raise Failure if there are further bytes *)
(** {2 Combinators} *)
val seq : ?stop:bool parser -> 'a parser -> 'a Seq.t parser
(** [seq p] is a sequence that uses [p] to get the next item.
A sequence node can only be used while the stream is at
the expected position, and will raise [Invalid_argument]
if any bytes have been consumed in the meantime. This
also means that each node can only be used once; use
{!Seq.memoize} to make the sequence persistent.
It is not necessary to consume all the elements of the
sequence.
Example ([head 4] is a parser that takes 4 lines):
{[
let head n r =
r |> Buf_read.(seq line) |> Seq.take n |> List.of_seq
]}
@param stop This is used before parsing each item.
The sequence ends if this returns [true].
The default is {!at_end_of_input}. *)
val pair : 'a parser -> 'b parser -> ('a * 'b) parser
(** [pair a b] is a parser that first uses [a] to parse a value [x],
then uses [b] to parse a value [y], then returns [(x, y)].
Note that this module does not support backtracking, so if [b] fails
then the bytes consumed by [a] are lost. *)
val return : 'a -> 'a parser
(** [return x] is a parser that consumes nothing and always returns [x].
[return] is just [Fun.const]. *)
val map : ('a -> 'b) -> ('a parser -> 'b parser)
(** [map f a] is a parser that parses the stream with [a] to get [v],
and then returns [f v]. *)
val bind : 'a parser -> ('a -> 'b parser) -> 'b parser
(** [bind a f] is a parser that first uses [a] to parse a value [v],
then uses [f v] to select the next parser, and then uses that. *)
val format_errors : 'a parser -> ('a, [> `Msg of string]) result parser
(** [format_errors p] catches [Failure], [End_of_file] and
[Buffer_limit_exceeded] exceptions and returns them as a formatted error message. *)
(** Convenient syntax for some of the combinators. *)
module Syntax : sig
val ( let+ ) : 'a parser -> ('a -> 'b) -> 'b parser
(** Syntax for {!map}. *)
val ( let* ) : 'a parser -> ('a -> 'b parser) -> 'b parser
(** Syntax for {!bind} *)
val ( and+ ) : 'a parser -> 'b parser -> ('a * 'b) parser
(** Syntax for {!pair} *)
val ( and* ) : 'a parser -> 'b parser -> ('a * 'b) parser
(** Syntax for {!pair} (same as [and+]). *)
val ( <*> ) : 'a parser -> 'b parser -> ('a * 'b) parser
(** [a <*> b] is [pair a b]. *)
val ( <* ) : 'a parser -> 'b parser -> 'a parser
(** [a <* b] is [map fst (pair a b)].
It parses two things and keeps only the first. *)
val ( *> ) : 'a parser -> 'b parser -> 'b parser
(** [a *> b] is [map snd (pair a b)].
It parses two things and keeps only the second. *)
end
(** {2 Low-level API} *)
val buffered_bytes : t -> int
(** [buffered_bytes t] is the number of bytes that can be read without
reading from the underlying flow. *)
val peek : t -> Cstruct.t
(** [peek t] returns a view onto the active part of [t]'s internal buffer.
Performing any operation that might add to the buffer may invalidate this,
so it should be used immediately and then forgotten.
[Cstruct.length (peek t) = buffered_bytes t]. *)
val ensure : t -> int -> unit
(** [ensure t n] ensures that the buffer contains at least [n] bytes of data.
If not, it reads from the flow until there is.
[buffered_bytes (ensure t n) >= n].
@raise End_of_file if the flow ended before [n] bytes were available
@raise Buffer_limit_exceeded if [n] exceeds the buffer's maximum size *)
val consume : t -> int -> unit
(** [consume t n] discards the first [n] bytes from [t]'s buffer.
Use this after {!peek} to mark some bytes as consumed.
[buffered_bytes t' = buffered_bytes t - n]
Note: unlike {!skip}, this will not read data from the underlying flow. *)
val consumed_bytes : t -> int
(** [consumed_bytes t] is the total number of bytes consumed.
i.e. it is the offset into the stream of the next byte to be parsed. *)
val eof_seen : t -> bool
(** [eof_seen t] indicates whether we've received [End_of_file] from the underlying flow.
If so, there will never be any further data beyond what [peek] already returns.
Note that this returns [false] if we're at the end of the stream but don't know it yet.
Use {!at_end_of_input} to be sure. *)

581
lib_eio/buf_write.ml Normal file
View File

@ -0,0 +1,581 @@
(* This module is based on code from Faraday (0.7.2), which had the following
license:
----------------------------------------------------------------------------
Copyright (c) 2016 Inhabited Type LLC.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the author nor the names of his contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------------*)
type bigstring = Bigstringaf.t
exception Dequeue_empty
module Deque(T:sig type t val sentinel : t end) : sig
type elem = T.t
type t
val create : int -> t
(* [t = create n] creates a new deque with initial capacity [n].
[to_list t = []] *)
val is_empty : t -> bool
(* [is_empty t = (to_list t = []) *)
val enqueue : elem -> t -> unit
(* [enqueue elem t]
[to_list t'] = to_list t @ [elem] *)
val dequeue_exn : t -> elem
(* [dequeue_exn t = List.hd (to_list t)]
[to_list t' = List.tl (to_list t)] *)
val enqueue_front : elem -> t -> unit
(* [enqueue_front elem t]
to_list t' = elem :: to_list t *)
val to_list : t -> elem list
end = struct
type elem = T.t
type t =
{ mutable elements : elem array
; mutable front : int
; mutable back : int }
let sentinel = T.sentinel
let create size =
{ elements = Array.make size sentinel; front = 0; back = 0 }
let is_empty t =
t.front = t.back
let ensure_space t =
if t.back = Array.length t.elements - 1 then begin
let len = t.back - t.front in
if t.front > 0 then begin
(* Shift everything to the front of the array and then clear out
* dangling pointers to elements from their previous locations. *)
Array.blit t.elements t.front t.elements 0 len;
Array.fill t.elements len t.front sentinel
end else begin
let old = t.elements in
let new_ = Array.(make (2 * length old) sentinel) in
Array.blit old t.front new_ 0 len;
t.elements <- new_
end;
t.front <- 0;
t.back <- len
end
let enqueue e t =
ensure_space t;
t.elements.(t.back) <- e;
t.back <- t.back + 1
let dequeue_exn t =
if is_empty t then
raise Dequeue_empty
else
let result = Array.unsafe_get t.elements t.front in
Array.unsafe_set t.elements t.front sentinel;
t.front <- t.front + 1;
result
let enqueue_front e t =
(* This is in general not true for Deque data structures, but the usage
* below ensures that there is always space to push an element back on the
* front. An [enqueue_front] is always preceded by a [dequeue], with no
* intervening operations. *)
assert (t.front > 0);
t.front <- t.front - 1;
t.elements.(t.front) <- e
let to_list t =
let result = ref [] in
for i = t.back - 1 downto t.front do
result := t.elements.(i) :: !result
done;
!result
end
module Buffers = Deque(struct
type t = Cstruct.t
let sentinel =
let deadbeef = "\222\173\190\239" in
Cstruct.of_string deadbeef
end)
module Flushes = Deque(struct
type t = int * ((unit, exn) result Promise.u)
let sentinel =
let _, r = Promise.create () in
Promise.resolve_ok r ();
0, r
end)
type state =
| Active
| Paused
| Closed
type t =
{ mutable buffer : bigstring
; mutable scheduled_pos : int (* How much of [buffer] is in [scheduled] *)
; mutable write_pos : int (* How much of [buffer] has been written to *)
; scheduled : Buffers.t
; flushed : Flushes.t
; mutable bytes_received : int (* Total scheduled bytes. Wraps. *)
; mutable bytes_written : int (* Total written bytes. Wraps. *)
; mutable state : state
; mutable wake_writer : unit -> unit
; mutable printf : (Format.formatter * bool ref) option
}
(* Invariant: [write_pos >= scheduled_pos] *)
exception Flush_aborted
let writable_exn t =
match t.state with
| Active | Paused -> ()
| Closed ->
failwith "cannot write to closed writer"
let wake_writer t =
match t.state with
| Paused -> ()
| Active | Closed ->
let wake = t.wake_writer in
if wake != ignore then (
t.wake_writer <- ignore;
wake ()
)
(* Schedule [cs] now, without any checks. Users use {!schedule_cstruct} instead. *)
let schedule_iovec t cs =
t.bytes_received <- t.bytes_received + Cstruct.length cs;
Buffers.enqueue cs t.scheduled
(* Schedule all pending data in [buffer]. *)
let flush_buffer t =
let len = t.write_pos - t.scheduled_pos in
if len > 0 then begin
let off = t.scheduled_pos in
schedule_iovec t (Cstruct.of_bigarray ~off ~len t.buffer);
t.scheduled_pos <- t.write_pos
end
let free_bytes_in_buffer t =
let buf_len = Bigstringaf.length t.buffer in
buf_len - t.write_pos
let schedule_cstruct t cs =
writable_exn t;
flush_buffer t;
if Cstruct.length cs > 0 then (
schedule_iovec t cs;
wake_writer t;
)
let ensure_space t len =
if free_bytes_in_buffer t < len then begin
flush_buffer t;
t.buffer <- Bigstringaf.create (max (Bigstringaf.length t.buffer) len);
t.write_pos <- 0;
t.scheduled_pos <- 0
end
let advance_pos t n =
t.write_pos <- t.write_pos + n;
wake_writer t
let write_gen t ~blit ~off ~len a =
writable_exn t;
ensure_space t len;
blit a ~src_off:off t.buffer ~dst_off:t.write_pos ~len;
advance_pos t len
let string =
let blit = Bigstringaf.blit_from_string in
fun t ?(off=0) ?len a ->
let len =
match len with
| None -> String.length a - off
| Some len -> len
in
write_gen t ~blit ~off ~len a
let bytes =
let blit = Bigstringaf.blit_from_bytes in
fun t ?(off=0) ?len a ->
let len =
match len with
| None -> Bytes.length a - off
| Some len -> len
in
write_gen t ~blit ~off ~len a
let cstruct t { Cstruct.buffer; off; len } =
write_gen t ~off ~len buffer
~blit:Bigstringaf.unsafe_blit
let char t c =
writable_exn t;
ensure_space t 1;
Bigstringaf.unsafe_set t.buffer t.write_pos c;
advance_pos t 1
let uint8 t b =
writable_exn t;
ensure_space t 1;
Bigstringaf.unsafe_set t.buffer t.write_pos (Char.unsafe_chr b);
advance_pos t 1
module BE = struct
let uint16 t i =
writable_exn t;
ensure_space t 2;
Bigstringaf.unsafe_set_int16_be t.buffer t.write_pos i;
advance_pos t 2
let uint32 t i =
writable_exn t;
ensure_space t 4;
Bigstringaf.unsafe_set_int32_be t.buffer t.write_pos i;
advance_pos t 4
let uint48 t i =
writable_exn t;
ensure_space t 6;
Bigstringaf.unsafe_set_int16_be t.buffer t.write_pos
Int64.(to_int (shift_right_logical i 32));
Bigstringaf.unsafe_set_int32_be t.buffer (t.write_pos + 2)
Int64.(to_int32 i);
advance_pos t 6
let uint64 t i =
writable_exn t;
ensure_space t 8;
Bigstringaf.unsafe_set_int64_be t.buffer t.write_pos i;
advance_pos t 8
let float t f =
writable_exn t;
ensure_space t 4;
Bigstringaf.unsafe_set_int32_be t.buffer t.write_pos (Int32.bits_of_float f);
advance_pos t 4
let double t d =
writable_exn t;
ensure_space t 8;
Bigstringaf.unsafe_set_int64_be t.buffer t.write_pos (Int64.bits_of_float d);
advance_pos t 8
end
module LE = struct
let uint16 t i =
writable_exn t;
ensure_space t 2;
Bigstringaf.unsafe_set_int16_le t.buffer t.write_pos i;
advance_pos t 2
let uint32 t i =
writable_exn t;
ensure_space t 4;
Bigstringaf.unsafe_set_int32_le t.buffer t.write_pos i;
advance_pos t 4
let uint48 t i =
writable_exn t;
ensure_space t 6;
Bigstringaf.unsafe_set_int16_le t.buffer t.write_pos
Int64.(to_int i);
Bigstringaf.unsafe_set_int32_le t.buffer (t.write_pos + 2)
Int64.(to_int32 (shift_right_logical i 16));
advance_pos t 6
let uint64 t i =
writable_exn t;
ensure_space t 8;
Bigstringaf.unsafe_set_int64_le t.buffer t.write_pos i;
advance_pos t 8
let float t f =
writable_exn t;
ensure_space t 4;
Bigstringaf.unsafe_set_int32_le t.buffer t.write_pos (Int32.bits_of_float f);
advance_pos t 4
let double t d =
writable_exn t;
ensure_space t 8;
Bigstringaf.unsafe_set_int64_le t.buffer t.write_pos (Int64.bits_of_float d);
advance_pos t 8
end
let close t =
t.state <- Closed;
flush_buffer t;
wake_writer t
let is_closed t =
match t.state with
| Closed -> true
| Active | Paused -> false
let abort t =
close t;
let rec aux () =
match Flushes.dequeue_exn t.flushed with
| exception Dequeue_empty -> ()
| (_threshold, r) ->
Promise.resolve_error r Flush_aborted;
aux ()
in
aux ()
let of_buffer ?sw buffer =
let t = { buffer
; write_pos = 0
; scheduled_pos = 0
; scheduled = Buffers.create 4
; flushed = Flushes.create 1
; bytes_received = 0
; bytes_written = 0
; state = Active
; wake_writer = ignore
; printf = None
}
in
begin match sw with
| Some sw -> Switch.on_release sw (fun () -> abort t)
| None -> ()
end;
t
let create ?sw size =
of_buffer ?sw (Bigstringaf.create size)
let pending_bytes t =
(t.write_pos - t.scheduled_pos) + (t.bytes_received - t.bytes_written)
let has_pending_output t =
pending_bytes t <> 0
let pause t =
match t.state with
| Active -> t.state <- Paused
| Paused | Closed -> ()
let unpause t =
match t.state with
| Active | Closed -> ()
| Paused ->
t.state <- Active;
if has_pending_output t then
wake_writer t
let flush t =
flush_buffer t;
unpause t;
if not (Buffers.is_empty t.scheduled) then (
let p, r = Promise.create () in
Flushes.enqueue (t.bytes_received, r) t.flushed;
Promise.await_exn p
)
let make_formatter t =
Format.make_formatter
(fun buf off len -> write_gen t buf ~off ~len ~blit:Bigstringaf.blit_from_string)
(fun () -> flush t)
let printf t =
let ppf, is_formatting =
match t.printf with
| Some (_, is_formatting as x) ->
is_formatting := true;
x
| None ->
let is_formatting = ref true in
let ppf =
Format.make_formatter
(fun buf off len -> write_gen t buf ~off ~len ~blit:Bigstringaf.blit_from_string)
(fun () ->
(* As per the Format module manual, an explicit flush writes to the
output channel and ensures that "all pending text is displayed"
and "these explicit flush calls [...] could dramatically impact efficiency".
Therefore it is clear that we need to call `flush t` instead of `flush_buffer t`. *)
if !is_formatting then flush t)
in
t.printf <- Some (ppf, is_formatting);
ppf, is_formatting
in
Format.kfprintf (fun ppf ->
if not !is_formatting then raise (Sys_error "Buf_write.printf: invalid concurrent access");
(* Ensure that [ppf]'s internal buffer is flushed to [t], but without flushing [t] itself: *)
is_formatting := false;
Format.pp_print_flush ppf ()
) ppf
let rec shift_buffers t written =
match Buffers.dequeue_exn t.scheduled with
| { Cstruct.len; _ } as iovec ->
if len <= written then
shift_buffers t (written - len)
else
Buffers.enqueue_front (Cstruct.shift iovec written) t.scheduled
| exception Dequeue_empty ->
assert (written = 0);
if t.scheduled_pos = t.write_pos then begin
t.scheduled_pos <- 0;
t.write_pos <- 0
end
(* Resolve any flushes that are now due. *)
let rec shift_flushes t =
match Flushes.dequeue_exn t.flushed with
| exception Dequeue_empty -> ()
| (threshold, r) as flush ->
(* Be careful: [bytes_written] and [threshold] both wrap, so subtract first. *)
if t.bytes_written - threshold >= 0 then (
(* We have written at least up to [threshold]
(or we're more than [max_int] behind, which we assume won't happen). *)
Promise.resolve_ok r ();
shift_flushes t
) else (
Flushes.enqueue_front flush t.flushed
)
let shift t written =
shift_buffers t written;
t.bytes_written <- t.bytes_written + written;
shift_flushes t
let rec await_batch t =
flush_buffer t;
match t.state, has_pending_output t with
| Closed, false -> raise End_of_file
| (Active | Closed), true -> Buffers.to_list t.scheduled
| Paused, _ | Active, false ->
Suspend.enter "Buf_write.await_batch" (fun ctx enqueue ->
Fiber_context.set_cancel_fn ctx (fun ex ->
t.wake_writer <- ignore;
enqueue (Error ex)
);
t.wake_writer <- (fun () ->
(* Our caller has already set [wake_writer <- ignore]. *)
Fiber_context.clear_cancel_fn ctx;
enqueue (Ok ())
);
);
await_batch t
(* We have to do our own copy, because we can't [shift] until the write is complete. *)
let copy t flow =
let rec aux () =
let iovecs = await_batch t in
let wrote = Flow.single_write flow iovecs in
shift t wrote;
aux ()
in
try aux ()
with End_of_file -> ()
let with_flow ?(initial_size=0x1000) flow fn =
Switch.run ~name:"Buf_write.with_flow" @@ fun sw ->
let t = create ~sw initial_size in
Fiber.fork ~sw (fun () -> copy t flow);
match fn t with
| x ->
close t;
x
| exception ex ->
close t;
(* Raising the exception will cancel the writer thread, so do a flush first.
We don't want to flush if cancelled, but in that case the switch will
end the writer thread itself (and [flush] will raise). *)
flush t;
raise ex
let rec serialize t writev =
match await_batch t with
| exception End_of_file -> Ok ()
| iovecs ->
match writev iovecs with
| Error `Closed as e -> close t; e
| Ok n ->
shift t n;
if not (Buffers.is_empty t.scheduled) then Fiber.yield ();
serialize t writev
let serialize_to_string t =
close t;
match await_batch t with
| exception End_of_file -> ""
| iovecs ->
let len = Cstruct.lenv iovecs in
let bytes = Bytes.create len in
let pos = ref 0 in
List.iter (function
| { Cstruct.buffer; off; len } ->
Bigstringaf.unsafe_blit_to_bytes buffer ~src_off:off bytes ~dst_off:!pos ~len;
pos := !pos + len)
iovecs;
shift t len;
assert (not (has_pending_output t));
Bytes.unsafe_to_string bytes
let serialize_to_cstruct t =
close t;
match await_batch t with
| exception End_of_file -> Cstruct.empty
| iovecs ->
let data = Cstruct.concat iovecs in
shift t (Cstruct.length data);
assert (not (has_pending_output t));
data
let drain =
let rec loop t acc =
match await_batch t with
| exception End_of_file -> acc
| iovecs ->
let len = Cstruct.lenv iovecs in
shift t len;
loop t (len + acc)
in
fun t -> loop t 0

334
lib_eio/buf_write.mli Normal file
View File

@ -0,0 +1,334 @@
(* This module is based on code from Faraday (0.7.2), which had the following
license:
----------------------------------------------------------------------------
Copyright (c) 2016 Inhabited Type LLC.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the author nor the names of his contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------------*)
(** Serialization primitives built for speed and memory-efficiency.
Buf_write is designed for writing fast and memory-efficient serializers.
It is based on the Faraday library, but adapted for Eio.
Its core type and related operation gives the user fine-grained control
over copying and allocation behavior while serializing user-defined types,
and presents the output in a form that makes it possible to use vectorized
write operations, such as the [writev][] system call, or any other platform
or application-specific output APIs.
A Buf_write serializer manages an internal buffer and a queue of output
buffers. The output buffers may be a sub range of the serializer's
internal buffer or one that is user-provided. Buffered writes such as
{!string}, {!char}, {!cstruct}, etc., copy the source bytes into the
serializer's internal buffer. Unbuffered writes are done with
{!schedule_cstruct}, which performs no copying. Instead, it enqueues the
source bytes into the serializer's write queue directly.
Example:
{[
module Write = Eio.Buf_write
let () =
Eio_mock.Backend.run @@ fun () ->
let stdout = Eio_mock.Flow.make "stdout" in
Write.with_flow stdout (fun w ->
Write.string w "foo";
Write.string w "bar";
Eio.Fiber.yield ();
Write.string w "baz";
)
]}
This combines the first two writes, giving:
{[
+stdout: wrote "foobar"
+stdout: wrote "baz"
]}
*)
type t
(** The type of a serializer. *)
exception Flush_aborted
(** Raised when waiting for a flush to complete if the buffer is destroyed instead. *)
(** {2 Running} *)
val with_flow : ?initial_size:int -> _ Flow.sink -> (t -> 'a) -> 'a
(** [with_flow flow fn] runs [fn writer], where [writer] is a buffer that flushes to [flow].
Concurrently with [fn], it also runs a fiber that copies from [writer] to [flow].
If this fiber runs out of data to copy then it will suspend itself.
Writing to [writer] will automatically schedule it to be resumed.
This means that pending data is flushed automatically before the process sleeps.
When [fn] returns, [writer] is automatically closed and any remaining data is flushed
before [with_flow] itself returns.
@param initial_size The initial size of the buffer used to collect writes.
New buffers will be allocated as needed, with the same size.
If the buffer is too small to contain a write, the size is increased. *)
(** {2 Buffered Writes}
A serializer manages an internal buffer for coalescing small writes. The
size of this buffer is determined when the serializer is created. If the
buffer does not contain sufficient space to service a caller's buffered
write, the serializer will allocate a new buffer of the sufficient size and
use it for the current and subsequent writes. The old buffer will be
garbage collected once all of its contents have been {!flush}ed. *)
val string : t -> ?off:int -> ?len:int -> string -> unit
(** [string t ?off ?len str] copies [str] into the serializer's
internal buffer. *)
val bytes : t -> ?off:int -> ?len:int -> Bytes.t -> unit
(** [bytes t ?off ?len bytes] copies [bytes] into the serializer's
internal buffer. It is safe to modify [bytes] after this call returns. *)
val cstruct : t -> Cstruct.t -> unit
(** [cstruct t cs] copies [cs] into the serializer's internal buffer.
It is safe to modify [cs] after this call returns.
For large cstructs, it may be more efficient to use {!schedule_cstruct}. *)
val printf : t -> ('a, Format.formatter, unit) format -> 'a
(** [printf t fmt ...] formats the arguments according to the format string [fmt].
It supports all formatting and pretty-printing features of the Format module.
The formatter's internal buffer is flushed to [t] after the call, without flushing [t] itself.
Explicit flushes (e.g. using [@.] or [%!]) perform a full (blocking) flush of [t]. *)
val make_formatter : t -> Format.formatter
(** [make_formatter t] creates a new formatter that writes to [t].
Flushing the formatter also flushes [t] itself. *)
val write_gen
: t
-> blit:('a -> src_off:int -> Cstruct.buffer -> dst_off:int -> len:int -> unit)
-> off:int
-> len:int
-> 'a -> unit
(** [write_gen t ~blit ~off ~len x] copies [x] into the serializer's
internal buffer using the provided [blit] operation.
See {!Bigstring.blit} for documentation of the arguments. *)
val char : t -> char -> unit
(** [char t c] copies [c] into the serializer's internal buffer. *)
val uint8 : t -> int -> unit
(** [uint8 t n] copies the lower 8 bits of [n] into the serializer's
internal buffer. *)
(** Big endian serializers *)
module BE : sig
val uint16 : t -> int -> unit
(** [uint16 t n] copies the lower 16 bits of [n] into the serializer's
internal buffer in big-endian byte order. *)
val uint32 : t -> int32 -> unit
(** [uint32 t n] copies [n] into the serializer's internal buffer in
big-endian byte order. *)
val uint48 : t -> int64 -> unit
(** [uint48 t n] copies the lower 48 bits of [n] into the serializer's
internal buffer in big-endian byte order. *)
val uint64 : t -> int64 -> unit
(** [uint64 t n] copies [n] into the serializer's internal buffer in
big-endian byte order. *)
val float : t -> float -> unit
(** [float t n] copies the lower 32 bits of [n] into the serializer's
internal buffer in big-endian byte order. *)
val double : t -> float -> unit
(** [double t n] copies [n] into the serializer's internal buffer in
big-endian byte order. *)
end
(** Little endian serializers *)
module LE : sig
val uint16 : t -> int -> unit
(** [uint16 t n] copies the lower 16 bits of [n] into the
serializer's internal buffer in little-endian byte order. *)
val uint32 : t -> int32 -> unit
(** [uint32 t n] copies [n] into the serializer's internal buffer in
little-endian byte order. *)
val uint48 : t -> int64 -> unit
(** [uint48 t n] copies the lower 48 bits of [n] into the serializer's
internal buffer in little-endian byte order. *)
val uint64 : t -> int64 -> unit
(** [uint64 t n] copies [n] into the serializer's internal buffer in
little-endian byte order. *)
val float : t -> float -> unit
(** [float t n] copies the lower 32 bits of [n] into the serializer's
internal buffer in little-endian byte order. *)
val double : t -> float -> unit
(** [double t n] copies [n] into the serializer's internal buffer in
little-endian byte order. *)
end
(** {2 Unbuffered Writes}
Unbuffered writes do not involve copying bytes to the serializer's internal
buffer. *)
val schedule_cstruct : t -> Cstruct.t -> unit
(** [schedule_cstruct t cs] schedules [cs] to be written.
[cs] is not copied in this process,
so [cs] should only be modified after [t] has been {!flush}ed. *)
(** {2 Querying A Serializer's State} *)
val free_bytes_in_buffer : t -> int
(** [free_bytes_in_buffer t] returns the free space, in bytes, of the
serializer's write buffer. If a write call has a length that exceeds
this value, the serializer will allocate a new buffer that will replace the
serializer's internal buffer for that and subsequent calls. *)
val has_pending_output : t -> bool
(** [has_pending_output t] is [true] if [t]'s output queue is non-empty. It may
be the case that [t]'s queued output is being serviced by some other thread
of control, but has not yet completed. *)
val pending_bytes : t -> int
(** [pending_bytes t] is the size of the next write, in bytes, that [t] will
surface to the caller via {!await_batch}. *)
(** {2 Control Operations} *)
val pause : t -> unit
(** [pause t] causes [t] to stop surfacing writes to the user.
This gives the serializer an opportunity to collect additional writes
before sending them to the underlying device, which will increase the write
batch size.
As one example, code may want to call this function if it's about to
release the OCaml lock and perform a blocking system call, but would like
to batch output across that system call.
Call {!unpause} to resume writing later.
Note that calling {!flush} or {!close} will automatically call {!unpause} too. *)
val unpause : t -> unit
(** [unpause t] resumes writing data after a previous call to {!pause}. *)
val flush : t -> unit
(** [flush t] waits until all prior writes have been successfully completed.
If [t] has no pending writes, [flush] returns immediately.
If [t] is paused then it is unpaused first.
@raise Flush_aborted if {!abort} is called before the data is written. *)
val close : t -> unit
(** [close t] closes [t]. All subsequent write calls will raise, and any
subsequent {!pause} calls will be ignored. If the serializer has
any pending writes, user code will have an opportunity to service them
before receiving [End_of_file]. Flush callbacks will continue to
be invoked while output is {!shift}ed out of [t] as needed. *)
val is_closed : t -> bool
(** [is_closed t] is [true] if [close] has been called on [t] and [false]
otherwise. A closed [t] may still have pending output. *)
(** {2 Low-level API}
Low-level operations for running a serializer. *)
val create : ?sw:Switch.t -> int -> t
(** [create ~sw len] creates a serializer with a fixed-length internal buffer of
length [len]. See the Buffered writes section for details about what happens
when [len] is not large enough to support a write.
@param sw When the switch is finished, {!abort} is called.
If you don't pass a switch, you may want to call [abort] manually on error. *)
val of_buffer : ?sw:Switch.t -> Cstruct.buffer -> t
(** [of_buffer ~sw buf] creates a serializer, using [buf] as its internal
buffer. The serializer takes ownership of [buf] until the serializer has
been closed and flushed of all output. *)
val abort : t -> unit
(** [abort t] is like {!close} followed by {!drain}, except that any pending
flush operations fail instead of completing successfully. *)
val await_batch : t -> Cstruct.t list
(** [await_batch t] returns a list of buffers that should be written.
If no data is currently available, it waits until some is.
After performing a write, call {!shift} with the number of bytes written.
You must accurately report the number of bytes written. Failure to do so
will result in the same bytes being surfaced multiple times.
@raises End_of_file [t] is closed and there is nothing left to write. *)
val shift : t -> int -> unit
(** [shift t n] removes the first [n] bytes in [t]'s write queue. Any flush
operations called within this span of the write queue will be scheduled
to resume. *)
(** {2 Convenience Functions}
These functions are included for testing, debugging, and general
development. They are not the suggested way of driving a serializer in a
production setting. *)
val serialize : t -> (Cstruct.t list -> (int, [`Closed]) result) -> (unit, [> `Closed]) result
(** [serialize t writev] calls [writev bufs] each time [t] is ready to write.
In the event that [writev] indicates a partial write, {!serialize} will
call {!Fiber.yield} before continuing. *)
val serialize_to_string : t -> string
(** [serialize_to_string t] runs [t], collecting the output into a string and
returning it. [serializie_to_string t] immediately closes [t]. *)
val serialize_to_cstruct : t -> Cstruct.t
(** [serialize_to_cstruct t] runs [t], collecting the output into a cstruct
and returning it. [serialize_to_cstruct t] immediately closes [t]. *)
val drain : t -> int
(** [drain t] removes all pending writes from [t], returning the number of
bytes that were enqueued to be written and freeing any scheduled
buffers in the process. Note that this does not close [t] itself,
and does not return until [t] has been closed. *)

119
lib_eio/condition.ml Normal file
View File

@ -0,0 +1,119 @@
(* Import these directly because we copy this file for the dscheck tests. *)
module Fiber_context = Eio__core.Private.Fiber_context
module Suspend = Eio__core.Private.Suspend
module Cancel = Eio__core.Cancel
type t = Broadcast.t
let create () = Broadcast.create ()
let lock_protected m =
Cancel.protect (fun () -> Eio_mutex.lock m)
let await_generic ?mutex t =
match
Suspend.enter_unchecked "Condition.await" (fun ctx enqueue ->
match Fiber_context.get_error ctx with
| Some ex ->
Option.iter Eio_mutex.unlock mutex;
enqueue (Error ex)
| None ->
match Broadcast.suspend t (fun () -> enqueue (Ok ())) with
| None ->
Option.iter Eio_mutex.unlock mutex
| Some request ->
Option.iter Eio_mutex.unlock mutex;
Fiber_context.set_cancel_fn ctx (fun ex ->
if Broadcast.cancel request then enqueue (Error ex)
(* else already succeeded *)
)
)
with
| () -> Option.iter lock_protected mutex
| exception ex ->
let bt = Printexc.get_raw_backtrace () in
Option.iter lock_protected mutex;
Printexc.raise_with_backtrace ex bt
let await t mutex = await_generic ~mutex t
let await_no_mutex t = await_generic t
let broadcast = Broadcast.resume_all
type request = Broadcast.request option
let register_immediate = Broadcast.suspend
let cancel = function
| Some request -> Broadcast.cancel request
| None -> false
let ensure_cancelled x = ignore (cancel x : bool)
type state =
| Init
| Waiting of ((unit, exn) result -> unit)
| Done
(* There main property want is that we don't suspend forever if a broadcast
happened after [fn] started, or if the fiber is cancelled.
1. We start in the Init state.
2. If a broadcast happens here we move to Done. If we later try to suspend, we'll resume immediately.
3. We run [fn]. If a broadcast happens during this we'll transition to Done as before.
4. If [fn] raises or wants to stop normally, we return without suspending at all.
5. Otherwise, we suspend the fiber.
6. We try to transition from Init to Waiting.
If a broadcast transitioned to Done before this, we resume immediately.
If a broadcast transitions afterwards, [wake] will see the [enqueue] function and wake us.
Therefore, we can only sleep forever if a broadcast never happens after starting [fn].
7. If the fiber is cancelled before suspending, we raise on suspend.
If cancelled after suspending and before the request succeeds, we cancel the request and raise.
If cancelled after the request succeeds, [wake] will resume us.
*)
let rec loop_no_mutex t fn =
let state = Atomic.make Init in
let wake () =
match Atomic.exchange state Done with
| Init -> () (* Broadcast happened before we suspended; suspend will notice *)
| Waiting enqueue -> enqueue (Ok ())
| Done -> assert false
in
let request = Broadcast.suspend t wake in
(* Note: to avoid memory leaks, make sure that [request] is finished in all cases. *)
match fn () with
| exception ex ->
let bt = Printexc.get_raw_backtrace () in
ensure_cancelled request;
Printexc.raise_with_backtrace ex bt
| Some x ->
ensure_cancelled request;
x
| None ->
Suspend.enter_unchecked "Condition.loop_no_mutex" (fun ctx enqueue ->
match Fiber_context.get_error ctx with
| Some ex ->
ensure_cancelled request;
(* If a broadcast already happened, we still cancel. *)
enqueue (Error ex)
| None ->
let waiting = Waiting enqueue in
if Atomic.compare_and_set state Init waiting then (
(* We were in Init, so [wake] hasn't yet done anything.
When it runs, it will resume us.
We're also not currently cancelled, because we checked above
and cancellations only come from the same thread. *)
Fiber_context.set_cancel_fn ctx (fun ex ->
if cancel request then (
(* We could set the state to Done here, but there's no need;
we're not racing with anything now. [wake] never runs. *)
enqueue (Error ex)
) (* else we already got resumed *)
)
) else (
(* State is already Done, but [wake] couldn't wake us then
because we hadn't moved to [waiting]. Resume now. *)
enqueue (Ok ())
)
);
loop_no_mutex t fn

103
lib_eio/condition.mli Normal file
View File

@ -0,0 +1,103 @@
(** Waiters call {!await} in a loop as long as some condition is false.
Fibers that modify inputs to the condition must call [broadcast] soon
afterwards so that waiters can re-check the condition.
Example:
{[
let x = ref 0
let cond = Eio.Condition.create ()
let mutex = Eio.Mutex.create ()
let set_x value =
Eio.Mutex.use_rw ~protect:false mutex (fun () -> x := value);
Eio.Condition.broadcast cond
let await_x p =
Eio.Mutex.use_ro mutex (fun () ->
while not (p !x) do (* [x] cannot change, as mutex is locked. *)
Eio.Condition.await cond mutex (* Mutex is unlocked while suspended. *)
done
)
]}
It is used like this:
{[
Fiber.both
(fun () ->
traceln "x = %d" !x;
await_x ((=) 42);
traceln "x = %d" !x
)
(fun () ->
set_x 5;
Fiber.yield ();
set_x 7;
set_x 42;
)
]}
*)
type t
val create : unit -> t
(** [create ()] creates a new condition variable. *)
val await : t -> Eio_mutex.t -> unit
(** [await t mutex] suspends the current fiber until it is notified by [t].
You should lock [mutex] before testing whether the condition is true,
and leave it locked while calling this function.
It will be unlocked while the fiber is waiting and locked again before
returning (it is also locked again if the wait is cancelled). *)
val await_no_mutex : t -> unit
(** [await_no_mutex t] suspends the current fiber until it is notified by [t].
This is only safe to use in the case where [t] is only used within a single domain,
and the test for the condition was done without switching fibers.
i.e. you know the condition is still false, and no notification of a change can be sent
until [await_no_mutex] has finished suspending the fiber. *)
val loop_no_mutex : t -> (unit -> 'a option) -> 'a
(** [loop_no_mutex t update] runs [update ()] until it returns [Some x], then returns [x].
If [update ()] returns [None] then it waits until {!broadcast} is called before retrying.
If {!broadcast} is called while [update] is running, [update] runs again immediately.
For example, if [broadcast config_changed] is performed after some configuration file is changed, then
you can ensure [load_config] will always eventually have seen the latest configuration like this:
{[
Fiber.fork_daemon ~sw (fun () ->
loop_no_mutex config_changed (fun () -> load_config (); None)
)
]}
Note that, since there is no lock, [load_config] may see a half-written update if the configuration
is changed again before it finishes reading it,
so it should just log the error and wait to be called again. *)
val broadcast : t -> unit
(** [broadcast t] wakes up any waiting fibers (by appending them to the run-queue to resume later).
If no fibers are waiting, nothing happens. *)
(** {2 Low-level API}
This is intended only for integrating Eio with other IO libraries. *)
type request
val register_immediate : t -> (unit -> unit) -> request
(** [register_immediate t fn] will call [fn ()] the next time {!broadcast} is called.
[fn] runs immediately from the caller's context, which might not be an Eio thread, or may be a signal handler, etc.
Therefore, care is needed here. This is typically used to send a wake-up event to some non-Eio library. *)
val cancel : request -> bool
(** [cancel request] tries to cancel a request created with {!register_unsafe}.
It returns [true] if the request was cancelled (the callback will never be called),
or [false] if the request was already complete (the callback has already been called). *)

108
lib_eio/core/broadcast.ml Normal file
View File

@ -0,0 +1,108 @@
(* See the Cells module for an overview of this system.
Each new waiter atomically increments the "suspend" pointer and writes
a callback there. The waking fiber removes all the callbacks and calls them.
In this version, "resume" never gets ahead of "suspend" (broadcasting just
brings it up-to-date with the "suspend" pointer).
When the resume fiber runs, some of the cells reserved for callbacks might
not yet have been filled. In this case, the resuming fiber just marks them
as needing to be resumed. When the suspending fiber continues, it will
notice this and continue immediately. *)
module Cell = struct
(* For any given cell, there are two actors running in parallel: the
suspender and the resumer.
The resumer only performs a single operation (resume).
The consumer waits to be resumed and then, optionally, cancels.
This means we only have three cases to think about:
1. Consumer adds request (Empty -> Request).
1a. Provider fulfills it (Request -> Resumed).
1b. Consumer cancels it (Request -> Cancelled).
2. Provider gets to cell first (Empty -> Resumed).
When the consumer tries to wait, it resumes immediately.
The Resumed state should never been seen. It exists only to allow the
request to be GC'd promptly. We could replace it with Empty, but having
separate states is clearer for debugging. *)
type _ t =
| Request of (unit -> unit)
| Cancelled
| Resumed
| Empty
let init = Empty
let segment_order = 2
let dump f = function
| Request _ -> Fmt.string f "Request"
| Empty -> Fmt.string f "Empty"
| Resumed -> Fmt.string f "Resumed"
| Cancelled -> Fmt.string f "Cancelled"
end
module Cells = Cells.Make(Cell)
type cell = unit Cell.t
type t = unit Cells.t
type request = unit Cells.segment * cell Atomic.t
let rec resume cell =
match (Atomic.get cell : cell) with
| Request r as cur ->
(* The common case: we have a waiter for the value *)
if Atomic.compare_and_set cell cur Resumed then r ();
(* else it was cancelled at the same time; ignore *)
| Empty ->
(* The consumer has reserved this cell but not yet stored the request.
We place Resumed there and it will handle it soon. *)
if Atomic.compare_and_set cell Empty Resumed then
() (* The consumer will deal with it *)
else
resume cell (* The Request was added concurrently; use it *)
| Cancelled -> ()
| Resumed ->
(* This state is unreachable because we (the provider) haven't set this yet *)
assert false
let cancel (segment, cell) =
match (Atomic.get cell : cell) with
| Request _ as old ->
if Atomic.compare_and_set cell old Cancelled then (
Cells.cancel_cell segment;
true
) else false (* We got resumed first *)
| Resumed -> false (* We got resumed first *)
| Cancelled -> invalid_arg "Already cancelled!"
| Empty ->
(* To call [cancel] the user needs a [request] value,
which they only get once we've reached the [Request] state.
[Empty] is unreachable from [Request]. *)
assert false
let suspend t k =
let (_, cell) as request = Cells.next_suspend t in
if Atomic.compare_and_set cell Empty (Request k) then Some request
else match Atomic.get cell with
| Resumed ->
(* Resumed before we could add the waiter *)
k ();
None
| Cancelled | Request _ | Empty ->
(* These are unreachable from the previously-observed non-Empty state
without us taking some action first *)
assert false
let resume_all t =
Cells.resume_all t resume
let create = Cells.make
let dump f t = Cells.dump f t

View File

@ -0,0 +1,37 @@
(** A lock-free queue of waiters that should all be resumed at once.
This uses {!Cells} internally. *)
type t
type request
(** A handle to a pending request that can be used to cancel it. *)
val create : unit -> t
(** [create ()] is a fresh broadcast queue. *)
val suspend : t -> (unit -> unit) -> request option
(** [suspend t fn] arranges for [fn ()] to be called on {!resume_all}.
[fn ()] may be called from the caller's context, or by [resume_all],
so it needs to be able to cope with running in any context where that
can run. For example, [fn] must be safe to call from a signal handler
if [resume_all] can be called from one. [fn] must not raise.
The returned request can be used to cancel. It can be [None] in the
(unlikely) event that [t] got resumed before the function returned. *)
val resume_all : t -> unit
(** [resume_all t] calls all non-cancelled callbacks attached to [t],
in the order in which they were suspended.
This function is lock-free and can be used safely even from a signal handler or GC finalizer. *)
val cancel : request -> bool
(** [cancel request] attempts to remove a pending request.
It returns [true] if the request was cancelled, or [false] if it got
resumed before that could happen. *)
val dump : Format.formatter -> t -> unit
(** Display the internal state of a queue, for debugging. *)

View File

@ -1,5 +1,4 @@
exception Cancelled = Exn.Cancelled exception Cancelled = Exn.Cancelled
exception Cancel_hook_failed = Exn.Cancel_hook_failed
type state = type state =
| On | On
@ -9,13 +8,13 @@ type state =
(* There is a tree of cancellation contexts for each domain. (* There is a tree of cancellation contexts for each domain.
A fiber is always in exactly one context, but can move to a new child and back (see [sub]). A fiber is always in exactly one context, but can move to a new child and back (see [sub]).
While a fiber is performing a cancellable operation, it sets a cancel function. While a fiber is performing a cancellable operation, it sets a cancel function.
When a context is cancelled, we attempt to call and remove each fiber's cancellation function, if any. When a context is cancelled, we call each fiber's cancellation function (first replacing it with [ignore]).
Cancelling always happens from the fiber's own domain, but the cancellation function may be removed Cancelling always happens from the fiber's own domain.
from another domain as soon as an operation is known to have succeeded. An operation may either finish normally or be cancelled (not both).
An operation may either finish normally or be cancelled; If a function can succeed in a separate domain,
whoever manages to clear the cancellation function is responsible for resuming the continuation. the user's cancel function is responsible for ensuring that this is done atomically. *)
If cancelled, this is done by calling the cancellation function. *)
type t = { type t = {
id : Trace.id;
mutable state : state; mutable state : state;
children : t Lwt_dllist.t; children : t Lwt_dllist.t;
fibers : fiber_context Lwt_dllist.t; fibers : fiber_context Lwt_dllist.t;
@ -23,10 +22,11 @@ type t = {
domain : Domain.id; (* Prevent access from other domains *) domain : Domain.id; (* Prevent access from other domains *)
} }
and fiber_context = { and fiber_context = {
tid : Ctf.id; tid : Trace.id;
mutable cancel_context : t; mutable cancel_context : t;
mutable cancel_node : fiber_context Lwt_dllist.node option; (* Our entry in [cancel_context.fibers] *) mutable cancel_node : fiber_context Lwt_dllist.node option; (* Our entry in [cancel_context.fibers] *)
cancel_fn : (exn -> unit) option Atomic.t; mutable cancel_fn : exn -> unit; (* Encourage the current operation to finish *)
mutable vars : Hmap.t;
} }
type _ Effect.t += Get_context : fiber_context Effect.t type _ Effect.t += Get_context : fiber_context Effect.t
@ -90,10 +90,12 @@ let move_fiber_to t fiber =
fiber.cancel_node <- Some new_node fiber.cancel_node <- Some new_node
(* Note: the new value is not linked into the cancellation tree. *) (* Note: the new value is not linked into the cancellation tree. *)
let create ~protected = let create ~protected purpose =
let children = Lwt_dllist.create () in let children = Lwt_dllist.create () in
let fibers = Lwt_dllist.create () in let fibers = Lwt_dllist.create () in
{ state = Finished; children; protected; fibers; domain = Domain.self () } let id = Trace.mint_id () in
Trace.create_cc id purpose;
{ id; state = Finished; children; protected; fibers; domain = Domain.self () }
(* Links [t] into the tree as a child of [parent] and returns a function to remove it again. *) (* Links [t] into the tree as a child of [parent] and returns a function to remove it again. *)
let activate t ~parent = let activate t ~parent =
@ -107,37 +109,38 @@ let activate t ~parent =
Lwt_dllist.remove node Lwt_dllist.remove node
(* Runs [fn] with a fresh cancellation context. *) (* Runs [fn] with a fresh cancellation context. *)
let with_cc ~ctx:fiber ~parent ~protected fn = let with_cc ~ctx:fiber ~parent ~protected purpose fn =
let t = create ~protected in if not protected then check parent;
let t = create ~protected purpose in
let deactivate = activate t ~parent in let deactivate = activate t ~parent in
move_fiber_to t fiber; move_fiber_to t fiber;
let cleanup () = move_fiber_to parent fiber; deactivate () in let cleanup () = move_fiber_to parent fiber; deactivate () in
match fn t with match fn t with
| x -> cleanup (); x | x -> cleanup (); Trace.exit_cc (); x
| exception ex -> cleanup (); raise ex | exception ex -> cleanup (); Trace.exit_cc (); raise ex
let protect fn = let protect fn =
let ctx = Effect.perform Get_context in let ctx = Effect.perform Get_context in
with_cc ~ctx ~parent:ctx.cancel_context ~protected:true @@ fun _ -> with_cc ~ctx ~parent:ctx.cancel_context ~protected:true Protect @@ fun _ ->
(* Note: there is no need to check the new context after [fn] returns; (* Note: there is no need to check the new context after [fn] returns;
the goal of cancellation is only to finish the thread promptly, not to report the error. the goal of cancellation is only to finish the thread promptly, not to report the error.
We also do not check the parent context, to make sure the caller has a chance to handle the result. *) We also do not check the parent context, to make sure the caller has a chance to handle the result. *)
fn () fn ()
let rec cancel_internal t ex acc_fns = (* Mark the cancellation tree rooted at [t] as Cancelling (stopping at protected sub-contexts),
let collect_cancel_fn fiber acc = and return a list of all fibers in the newly-cancelling contexts. Since modifying the cancellation
match Atomic.exchange fiber.cancel_fn None with tree can only be done from our domain, this is effectively an atomic operation. Once it returns,
| None -> acc (* The operation succeeded and so can't be cancelled now *) new (non-protected) fibers cannot be added to any of the cancelling contexts. *)
| Some cancel_fn -> cancel_fn :: acc let rec cancel_internal t ex acc_fibers =
in
match t.state with match t.state with
| Finished -> invalid_arg "Cancellation context finished!" | Finished -> invalid_arg "Cancellation context finished!"
| Cancelling _ -> acc_fns | Cancelling _ -> acc_fibers
| On -> | On ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
t.state <- Cancelling (ex, bt); t.state <- Cancelling (ex, bt);
let acc_fns = Lwt_dllist.fold_l collect_cancel_fn t.fibers acc_fns in Trace.error t.id ex;
Lwt_dllist.fold_r (cancel_child ex) t.children acc_fns let acc_fibers = Lwt_dllist.fold_r List.cons t.fibers acc_fibers in
Lwt_dllist.fold_r (cancel_child ex) t.children acc_fibers
and cancel_child ex t acc = and cancel_child ex t acc =
if t.protected then acc if t.protected then acc
else cancel_internal t ex acc else cancel_internal t ex acc
@ -147,33 +150,43 @@ let check_our_domain t =
let cancel t ex = let cancel t ex =
check_our_domain t; check_our_domain t;
let fns = cancel_internal t ex [] in let fibers = cancel_internal t ex [] in
let cex = Cancelled ex in let cex = Cancelled ex in
let rec aux = function let rec aux = function
| [] -> [] | [] -> []
| fn :: fns -> | x :: xs ->
let fn = x.cancel_fn in
x.cancel_fn <- ignore;
match fn cex with match fn cex with
| () -> aux fns | () -> aux xs
| exception ex2 -> ex2 :: aux fns | exception ex2 ->
let bt = Printexc.get_raw_backtrace () in
(ex2, bt) :: aux xs
in in
if fns <> [] then ( if fibers <> [] then (
match protect (fun () -> aux fns) with match aux fibers with
| [] -> () | [] -> ()
| exns -> raise (Cancel_hook_failed exns) | ex :: exs ->
let ex, bt = List.fold_left Exn.combine ex exs in
Printexc.raise_with_backtrace ex bt
) )
let sub fn = let sub_checked ?name purpose fn =
let ctx = Effect.perform Get_context in let ctx = Effect.perform Get_context in
let parent = ctx.cancel_context in let parent = ctx.cancel_context in
with_cc ~ctx ~parent ~protected:false @@ fun t -> with_cc ~ctx ~parent ~protected:false purpose @@ fun t ->
Option.iter (Trace.name t.id) name;
fn t fn t
let sub fn =
sub_checked Sub fn
(* Like [sub], but it's OK if the new context is cancelled. (* Like [sub], but it's OK if the new context is cancelled.
(instead, return the parent context on exit so the caller can check that) *) (instead, return the parent context on exit so the caller can check that) *)
let sub_unchecked fn = let sub_unchecked purpose fn =
let ctx = Effect.perform Get_context in let ctx = Effect.perform Get_context in
let parent = ctx.cancel_context in let parent = ctx.cancel_context in
with_cc ~ctx ~parent ~protected:false @@ fun t -> with_cc ~ctx ~parent ~protected:false purpose @@ fun t ->
fn t; fn t;
parent parent
@ -186,24 +199,37 @@ module Fiber_context = struct
let get_error t = get_error t.cancel_context let get_error t = get_error t.cancel_context
let set_cancel_fn t fn = let set_cancel_fn t fn =
(* if Atomic.exchange t.cancel_fn (Some fn) <> None then failwith "Fiber already has a cancel function!" *) t.cancel_fn <- fn
Atomic.set t.cancel_fn (Some fn)
let clear_cancel_fn t = let clear_cancel_fn t =
Atomic.exchange t.cancel_fn None <> None t.cancel_fn <- ignore
let make ~cc = let make ~cc ~vars =
let tid = Ctf.mint_id () in let tid = Trace.mint_id () in
Ctf.note_created tid Ctf.Task; Trace.create_fiber tid ~cc:cc.id;
let t = { tid; cancel_context = cc; cancel_node = None; cancel_fn = Atomic.make None } in let t = { tid; cancel_context = cc; cancel_node = None; cancel_fn = ignore; vars } in
t.cancel_node <- Some (Lwt_dllist.add_r t cc.fibers); t.cancel_node <- Some (Lwt_dllist.add_r t cc.fibers);
t t
let make_root () = let make_root () =
let cc = create ~protected:false in let cc = create ~protected:false Root in
cc.state <- On; cc.state <- On;
make ~cc make ~cc ~vars:Hmap.empty
let destroy t = let destroy t =
Trace.exit_fiber t.tid;
Option.iter Lwt_dllist.remove t.cancel_node Option.iter Lwt_dllist.remove t.cancel_node
let vars t = t.vars
let get_vars () =
vars (Effect.perform Get_context)
let with_vars t vars fn =
let old_vars = t.vars in
t.vars <- vars;
let cleanup () = t.vars <- old_vars in
match fn () with
| x -> cleanup (); x
| exception ex -> cleanup (); raise ex
end end

483
lib_eio/core/cells.ml Normal file
View File

@ -0,0 +1,483 @@
module type CELL = sig
type 'a t
val init : 'a t
val segment_order : int
val dump : _ t Fmt.t
end
(* To avoid worrying about wrapping on 32-bit platforms,
we use 63-bit integers for indexes in all cases.
On 64-bit platforms, this is just [int]. *)
module Int63 = struct
include Optint.Int63
(* Fallback for 32-bit platforms. *)
let rec fetch_and_add_fallback t delta =
let old = Atomic.get t in
if Atomic.compare_and_set t old (add old (of_int delta)) then old
else fetch_and_add_fallback t delta
let fetch_and_add : t Atomic.t -> int -> t =
match is_immediate with
| True -> Atomic.fetch_and_add
| False -> fetch_and_add_fallback
end
module Make(Cell : CELL) = struct
let cells_per_segment = 1 lsl Cell.segment_order
let segment_mask = cells_per_segment - 1
(* An index identifies a cell. It is a pair of the segment ID and the offset
within the segment, packed into a single integer so we can increment it
atomically. *)
module Index : sig
type t
type segment_id = Int63.t
val of_segment : segment_id -> t
(* [of_segment x] is the index of the first cell in segment [x]. *)
val segment : t -> segment_id
val offset : t -> int
val zero : t
val succ : t -> t
val pred : t -> t
val next : t Atomic.t -> t
(* val pp : t Fmt.t *)
end = struct
type t = Int63.t
type segment_id = Int63.t
let segment t = Int63.shift_right_logical t Cell.segment_order
let of_segment id = Int63.shift_left id Cell.segment_order
let offset t = Int63.to_int t land segment_mask
let zero = Int63.zero
let succ = Int63.succ
let pred = Int63.pred
let next t_atomic =
Int63.fetch_and_add t_atomic (+1)
(* let pp f t = Fmt.pf f "%d:%d" (segment t) (offset t) *)
end
(* A pair with counts for the number of cancelled cells in a segment and the
number of pointers to it, packed as an integer so it can be adjusted atomically. *)
module Count : sig
type t
val create : pointers:int -> t
(* [create ~pointers] is a new counter for a segment.
Initially there are no cancelled cells. *)
val removed : t -> bool
(* [removed t] is true if a segment with this count should be removed
(i.e. all cells are cancelled and it has no pointers).
Once this returns [true], it will always return [true] in future. *)
val incr_cancelled : t -> bool
(* Increment the count of cancelled cells, then return [removed t] for the new state. *)
val try_inc_pointers : t -> bool
(* Atomically increment the pointers count, unless [removed t].
Returns [true] on success. *)
val dec_pointers : t -> bool
(* Decrement the pointers count, then return [removed t] for the new state. *)
val validate : expected_pointers:int -> t -> unit
(* [validate ~expected_pointers t] check that [t] is a valid count for a non-removed segment. *)
val dump : t Fmt.t
end = struct
type t = int Atomic.t
(* We use 16 bits for the cancelled count, which should be plenty.
The remaining bits (at least 15) are used for the pointer count,
which normally doesn't go above 2 (except temporarily, and limited
by the number of domains). *)
let () = assert (cells_per_segment < 0x10000)
let v ~pointers ~cancelled = (pointers lsl 16) lor cancelled
let v_removed = v ~pointers:0 ~cancelled:cells_per_segment
let pointers v = v lsr 16
let cancelled v = v land 0xffff
let create ~pointers = Atomic.make (v ~pointers ~cancelled:0)
let dump f t =
let v = Atomic.get t in
Fmt.pf f "pointers=%d, cancelled=%d" (pointers v) (cancelled v)
let incr_cancelled t =
Atomic.fetch_and_add t 1 = v_removed - 1
let rec try_inc_pointers t =
let v = Atomic.get t in
if v = v_removed then false
else (
if Atomic.compare_and_set t v (v + (1 lsl 16)) then true
else try_inc_pointers t
)
let dec_pointers t =
Atomic.fetch_and_add t (-1 lsl 16) = v_removed + (1 lsl 16)
let removed t =
Atomic.get t = v_removed
let validate ~expected_pointers t =
let v = Atomic.get t in
assert (cancelled v >= 0 && cancelled v <= cells_per_segment);
if cancelled v = cells_per_segment then assert (pointers v > 0);
if pointers v <> expected_pointers then
Fmt.failwith "Bad pointer count!"
end
(* A segment is a node in a linked list containing an array of [cells_per_segment] cells. *)
module Segment : sig
type 'a t
val make_init : unit -> 'a t
(* [make_init ()] is a new initial segment. *)
val id : _ t -> Index.segment_id
val get : 'a t -> int -> 'a Cell.t Atomic.t
(* [get t offset] is the cell at [offset] within [t]. *)
val try_inc_pointers : _ t -> bool
(* Atomically increment the pointers count if the segment isn't removed.
Returns [true] on success, or [false] if the segment was removed first. *)
val dec_pointers : _ t -> unit
(* Decrement the pointers count, removing the segment if it is no longer
needed. *)
val find : 'a t -> Index.segment_id -> 'a t
(* [find t id] finds the segment [id] searching forwards from [t].
If the target segment has not yet been created, this creates it (atomically).
If the target segment has been removed, this returns the next non-removed segment. *)
val clear_prev : _ t -> unit
(* Called when the resumer has reached this segment,
so it will never need to skip over any previous segments.
Therefore, the previous pointer is no longer required and
previous segments can be GC'd. *)
val cancel_cell : _ t -> unit
(* Increment the cancelled-cells counter, and remove the segment if it is no longer useful. *)
val validate : 'a t -> suspend:'a t -> resume:'a t -> unit
(* [validate t ~suspend ~resume] checks that [t] is in a valid state,
assuming there are no operations currently in progress.
[suspend] and [resume] are the segments of the suspend and resume pointers.
It checks that both are reachable from [t]. *)
val dump_list : label:Index.t Fmt.t -> 'a t Fmt.t
(* [dump_list] formats this segment and all following ones for debugging.
@param label Used to annotate indexes. *)
end = struct
type 'a t = {
id : Index.segment_id;
count : Count.t;
cells : 'a Cell.t Atomic.t array;
prev : 'a t option Atomic.t; (* None if first, or [prev] is no longer needed *)
next : 'a t option Atomic.t; (* None if not yet created *)
}
let id t = t.id
let get t i = Array.get t.cells i
let pp_id f t = Int63.pp f t.id
let dump_cells ~label f t =
let idx = ref (Index.of_segment t.id) in
for i = 0 to Array.length t.cells - 1 do
Fmt.pf f "@,%a" Cell.dump (Atomic.get t.cells.(i));
label f !idx;
idx := Index.succ !idx
done
let rec dump_list ~label f t =
Fmt.pf f "@[<v2>Segment %a (prev=%a, %a):%a@]"
pp_id t
(Fmt.Dump.option pp_id) (Atomic.get t.prev)
Count.dump t.count
(dump_cells ~label) t;
let next = Atomic.get t.next in
begin match next with
| Some next when next.id = Int63.succ t.id ->
() (* We'll show the labels at the start of the next segment *)
| _ ->
Fmt.pf f "@,End%a"
label (Index.of_segment (Int63.succ t.id))
end;
Option.iter (fun next -> Fmt.cut f (); dump_list ~label f next) next
let next t =
match Atomic.get t.next with
| Some s -> s
| None ->
let next = {
id = Int63.succ t.id;
count = Count.create ~pointers:0;
cells = Array.init cells_per_segment (fun (_ : int) -> Atomic.make Cell.init);
next = Atomic.make None;
prev = Atomic.make (Some t);
} in
if Atomic.compare_and_set t.next None (Some next) then next
else Atomic.get t.next |> Option.get
let removed t =
Count.removed t.count
(* Get the previous non-removed segment, if any. *)
let rec alive_prev t =
match Atomic.get t.prev with
| Some prev when removed prev -> alive_prev prev
| x -> x
(* Get the next non-removed segment. *)
let alive_next t =
let next = Atomic.get t.next |> Option.get in
let rec live x =
if removed x then (
match Atomic.get x.next with
| Some next -> live next
| None -> x (* The paper says to return "tail if all are removed", but can that ever happen? *)
) else x
in
live next
(* Remove [t] from the linked-list by splicing together
the previous live segment before us to the next live one afterwards.
The tricky case is when two adjacent segments get removed at the same time.
If that happens, the next and prev lists will still always be valid
(i.e. will include all live segments, in the correct order), but may not be optimal.
However, we will detect that case when it happens and fix it up immediately. *)
let rec remove t =
if Atomic.get t.next = None then () (* Can't remove tail. This shouldn't happen anyway. *)
else (
let prev = alive_prev t
and next = alive_next t in
(* [prev] might have been removed by the time we do this, but it doesn't matter,
we're still only skipping removed segments (just not as many as desired).
We'll fix it up afterwards in that case. *)
Atomic.set next.prev prev;
(* Likewise [next] might have been removed too by now, but we'll correct later. *)
Option.iter (fun prev -> Atomic.set prev.next (Some next)) prev;
(* If either got removed by now, start again. *)
if removed next && Atomic.get next.next <> None then remove t
else match prev with
| Some prev when removed prev -> remove t
| _ -> ()
)
let try_inc_pointers t =
Count.try_inc_pointers t.count
let dec_pointers t =
if Count.dec_pointers t.count then remove t
let cancel_cell t =
if Count.incr_cancelled t.count then remove t
let rec find start id =
if start.id >= id && not (removed start) then start
else find (next start) id
let make_init () =
{
id = Int63.zero;
count = Count.create ~pointers:2;
cells = Array.init cells_per_segment (fun (_ : int) -> Atomic.make Cell.init);
next = Atomic.make None;
prev = Atomic.make None;
}
(* Note: this assumes the system is at rest (no operations in progress). *)
let rec validate t ~suspend ~resume ~seen_pointers =
let expected_pointers =
(if t == suspend then 1 else 0) +
(if t == resume then 1 else 0)
in
Count.validate ~expected_pointers t.count;
let seen_pointers = seen_pointers + expected_pointers in
match Atomic.get t.next with
| None -> assert (seen_pointers = 2)
| Some next ->
begin match Atomic.get next.prev with
| None -> assert (resume.id >= next.id)
| Some t2 -> assert (resume.id < next.id && t == t2)
end;
validate next ~suspend ~resume ~seen_pointers
let validate = validate ~seen_pointers:0
let clear_prev t =
Atomic.set t.prev None
end
(* A mutable pointer into the list of cells. *)
module Position : sig
type 'a t
val of_segment : 'a Segment.t -> 'a t
(* [of_segment x] is a pointer to the first cell in [x]. *)
val next : clear_prev:bool -> 'a t -> 'a Segment.t * 'a Cell.t Atomic.t
(* [next t ~clear_prev] returns the segment and cell of [t], and atomically increments it.
If [t]'s segment is all cancelled and no longer exists it will skip it and retry.
If [clear_prev] then the previous pointer is no longer required. *)
val resume_all : 'a t -> stop:Index.t -> ('a Cell.t Atomic.t -> unit) -> unit
(* [resume_all t ~stop f] advances [t] to [stop], then calls [f cell] on each cell advanced over. *)
val index : _ t -> Index.t
(* [index t] is the index of the cell currently pointed-to by [t]. *)
val segment : 'a t -> 'a Segment.t
(* For debugging only. The segment containing the previously-returned cell (or the initial segment),
when the system is at rest. *)
end = struct
type 'a t = {
segment : 'a Segment.t Atomic.t; (* Note: can lag [idx] *)
idx : Index.t Atomic.t;
}
let segment t = Atomic.get t.segment
let index t = Atomic.get t.idx
let of_segment segment =
{
segment = Atomic.make segment;
idx = Atomic.make Index.zero;
}
(* Set [t.segment] to [target] if [target] is ahead of us.
Returns [false] if [target] gets removed first. *)
let rec move_forward t (target : _ Segment.t) =
let cur = Atomic.get t.segment in
if Segment.id cur >= Segment.id target then true
else (
if not (Segment.try_inc_pointers target) then false (* target already removed *)
else (
if Atomic.compare_and_set t.segment cur target then (
Segment.dec_pointers cur;
true
) else (
(* Concurrent update of [t]. Undo ref-count changes and retry. *)
Segment.dec_pointers target;
move_forward t target
)
)
)
(* Update [t] to the segment [id] (or the next non-removed segment after it). *)
let rec find_and_move_forward t start id =
let target = Segment.find start id in
if move_forward t target then target
else find_and_move_forward t start id (* Removed before we could increase the ref-count; rety *)
let rec next ~clear_prev t =
(* Get the segment first before the index. Even if [idx] moves forwards after this,
we'll still be able to reach it from [r]. *)
let r = Atomic.get t.segment in
let i = Index.next t.idx in
let id = Index.segment i in
let s = find_and_move_forward t r id in
if clear_prev then Segment.clear_prev s;
if Segment.id s = id then (
(s, Segment.get s (Index.offset i))
) else (
(* The segment we wanted contains only cancelled cells.
Try to update the index to jump over those cells, then retry. *)
let s_index = Index.of_segment (Segment.id s) in
ignore (Atomic.compare_and_set t.idx (Index.succ i) s_index : bool);
next ~clear_prev t
)
let rec resume_all t ~stop f =
(* Get the segment first before the index. Even if [idx] moves forwards after this,
we'll still be able to reach it from [start_seg]. *)
let start_seg = Atomic.get t.segment in
let start = Atomic.get t.idx in
if start >= stop then ()
else if not (Atomic.compare_and_set t.idx start stop) then (
resume_all t ~stop f
) else (
(* We are now responsible for resuming all cells from [start] to [stop]. *)
(* Move [t.segment] forward so we can free older segments now. *)
ignore (find_and_move_forward t start_seg (Index.segment (Index.pred stop)) : _ Segment.t);
(* Resume all cells from [i] to [stop] (reachable via [seg]): *)
let rec aux seg i =
if i < stop then (
let seg = Segment.find seg (Index.segment i) in
Segment.clear_prev seg;
let seg_start = Index.of_segment (Segment.id seg) in
if seg_start < stop then (
let i = max i seg_start in
f (Segment.get seg (Index.offset i));
aux seg (Index.succ i)
)
)
in
aux start_seg start
)
end
type 'a t = {
resume : 'a Position.t;
suspend : 'a Position.t;
}
type 'a segment = 'a Segment.t
let next_suspend t =
Position.next t.suspend ~clear_prev:false
let next_resume t =
snd @@ Position.next t.resume ~clear_prev:true
let resume_all t f =
Position.resume_all t.resume ~stop:(Position.index t.suspend) f
let cancel_cell = Segment.cancel_cell
let make () =
let init = Segment.make_init () in
{
resume = Position.of_segment init;
suspend = Position.of_segment init;
}
let validate t =
let suspend = Position.segment t.suspend in
let resume = Position.segment t.resume in
let start =
if Segment.id suspend < Segment.id resume then suspend
else resume
in
Segment.validate start ~suspend ~resume
let dump f t =
let suspend = Position.index t.suspend in
let resume = Position.index t.resume in
let start =
if suspend < resume then t.suspend
else t.resume
in
let label f i =
if i = suspend then Format.pp_print_string f " (suspend)";
if i = resume then Format.pp_print_string f " (resume)";
in
Format.fprintf f "@[<v>%a@]" (Segment.dump_list ~label) (Position.segment start)
end

111
lib_eio/core/cells.mli Normal file
View File

@ -0,0 +1,111 @@
(** A lock-free queue-like structure with suspension and cancellation.
This module provides an infinite sequence of atomic cells, which can be used for whatever you like.
There are two pointers into this sequence: a suspend (consumer) pointer and a resume (producer) pointer.
These are similar to the head and tail pointers in a traditional queue,
except that the consumer is also permitted to get ahead of the producer.
To use this as a plain queue, each producer calls {!Make.next_resume} to get the
cell at the resume (tail) pointer (and advance it atomically), then stores
its value in the cell. Each consumer calls {!Make.next_suspend} to get the next
cell at the head of the queue (and advance the suspend pointer).
The consumer/suspender is permitted to get ahead of the producer. In this
case, the consumer will CAS the cell from its initial state to a Request
state containing a callback to receive the value when it arrives. When a
producer later tries to CAS the cell from the initial state to holding a
value, it will fail and find the Request with the callback function
instead. It can then provide the value directly to the callback.
A suspender can be cancelled by CASing the Request to a Cancelled state.
It should also call {!Make.cancel_cell} (if the CAS succeeds), to allow the cell to be freed.
If a resumer's CAS fails because the cell is cancelled, it can retry with a fresh cell.
For efficiency, cells are grouped into segments, which are stored in a linked list.
Once all the cells in a segment are cancelled, the whole segment may be freed.
This is based on {{:https://arxiv.org/pdf/2111.12682.pdf}A formally-verified
framework for fair synchronization in kotlin coroutines, Appendix C},
which contains more details and examples of use.
This module also adds the {!Make.resume_all} function, which is useful for broadcasting.
*)
(** The signature for user-defined cell contents. *)
module type CELL = sig
type 'a t
val init : 'a t
(** The value to give newly-allocated cells. *)
val segment_order : int
(** The number of bits to use for the offset into the segment.
The number of cells per segment is [2 ** segment_order]. *)
val dump : _ t Fmt.t
(** Display the cell state for debugging. *)
end
module Make(Cell : CELL) : sig
type 'a t
type 'a segment
val make : unit -> 'a t
(** [make ()] is a fresh sequence of cells. *)
val next_suspend : 'a t -> 'a segment * 'a Cell.t Atomic.t
(** [next_suspend t] atomically returns the next suspend cell and its segment.
If multiple domains call this at the same time, they will each get a different location.
The cell might or might not have already been filled in by a resumer.
You need to handle both cases (typically by using {!Atomic.compare_and_set}).
The segment can be used with {!cancel_cell}.
This function is lock-free and is safe to call even from a signal handler or GC finalizer. *)
val next_resume : 'a t -> 'a Cell.t Atomic.t
(** [next_resume t] atomically returns the next resume cell.
If multiple domains call this at the same time, they will each get a different cell.
The cell might or might not contain a request from a suspender that got there first.
You need to handle both cases (typically by using {!Atomic.compare_and_set}).
Note: cancelled cells may or may not be skipped (you need to handle the case of the
cell you get being cancelled before you can write to it, but you also
can't rely on seeing every cancelled cell, as cancelled segments may be deleted).
This function is lock-free and is safe to call even from a signal handler or GC finalizer. *)
val resume_all : 'a t -> ('a Cell.t Atomic.t -> unit) -> unit
(** [resume_all t f] advances the resume position to the current suspend position,
then calls [f cell] on each cell advanced over.
Note: as with {!next_resume}, [f] may be called for some cancelled cells but not others.
[f] must not raise an exception (if it does, it will not be called on the remaining cells).
If the resume position is ahead of the suspend position, then calling this function does nothing.
This function is lock-free and is safe to call even from a signal handler or GC finalizer. *)
val cancel_cell : 'a segment -> unit
(** [cancel_cell segment] increments the segment's count of the number of cancelled cells.
Once all cells are cancelled it may be possible to discard the whole segment.
This avoids leaking memory if a user keeps suspending and then cancelling.
You must not call this more than once per cell.
This function is lock-free and is safe to call even from a signal handler or GC finalizer. *)
val validate : _ t -> unit
(** [validate t] checks that [t] is in a valid state, assuming there are no operations currently in progress. *)
val dump : _ t Fmt.t
(** [dump] outputs the internal state of a [_ t], for debugging. *)
end

51
lib_eio/core/debug.ml Normal file
View File

@ -0,0 +1,51 @@
type traceln = {
traceln : 'a. ?__POS__:string * int * int * int -> ('a, Format.formatter, unit, unit) format4 -> 'a;
} [@@unboxed]
let traceln_key : traceln Fiber.key = Fiber.create_key ()
let traceln_mutex = Mutex.create ()
let default_traceln ?__POS__:pos fmt =
let k go =
Trace.with_span "traceln" @@ fun () ->
let b = Buffer.create 512 in
let f = Format.formatter_of_buffer b in
go f;
Option.iter (fun (file, lnum, _, _) -> Format.fprintf f " [%s:%d]" file lnum) pos;
Format.pp_close_box f ();
Format.pp_print_flush f ();
let msg = Buffer.contents b in
Trace.log msg;
let lines = String.split_on_char '\n' msg in
Mutex.lock traceln_mutex;
Fun.protect ~finally:(fun () -> Mutex.unlock traceln_mutex) @@ fun () ->
List.iter (Printf.eprintf "+%s\n") lines;
flush stderr
in
Format.kdprintf k ("@[" ^^ fmt)
let get () =
match Fiber.get traceln_key with
| Some traceln -> traceln
| None
| exception (Effect.Unhandled _) -> { traceln = default_traceln }
let with_trace_prefix prefix fn =
let { traceln } = get () in
let traceln ?__POS__ fmt =
traceln ?__POS__ ("%t" ^^ fmt) prefix
in
Fiber.with_binding traceln_key { traceln } fn
let traceln ?__POS__ fmt =
let { traceln } = get () in
traceln ?__POS__ fmt
type t = <
traceln : traceln Fiber.key;
>
let v = object
method traceln = traceln_key
end

4
lib_eio/core/dune Normal file
View File

@ -0,0 +1,4 @@
(library
(name eio__core)
(public_name eio.core)
(libraries hmap lwt-dllist fmt optint eio.runtime_events))

22
lib_eio/core/eio__core.ml Normal file
View File

@ -0,0 +1,22 @@
module Promise = Promise
module Fiber = Fiber
module Switch = Switch
module Cancel = Cancel
module Exn = Exn
module Private = struct
module Suspend = Suspend
module Cells = Cells
module Broadcast = Broadcast
module Single_waiter = Single_waiter
module Trace = Trace
module Fiber_context = Cancel.Fiber_context
module Debug = Debug
module Effects = struct
type 'a enqueue = 'a Suspend.enqueue
type _ Effect.t +=
| Suspend = Suspend.Suspend
| Fork = Fiber.Fork
| Get_context = Cancel.Get_context
end
end

785
lib_eio/core/eio__core.mli Normal file
View File

@ -0,0 +1,785 @@
(** Private internal module. Use {!Eio} instead. *)
(** @canonical Eio.Switch *)
module Switch : sig
(** Many resources in Eio (such as fibers and file handles) require a switch to
be provided when they are created. The resource cannot outlive its switch.
If a function wants to create such resources, and was not passed a switch
as an argument, it will need to create a switch using {!run}.
This doesn't return until all resources attached to it have been freed,
preventing the function from leaking resources.
Any function creating resources that outlive it needs to be given a
switch by its caller.
Each switch includes its own {!Cancel.t} context.
Calling {!fail} cancels all fibers attached to the switch and, once they
have exited, reports the error.
Note: this concept is known as a "nursery" or "bundle" in some other systems.
Example:
{[
Switch.run (fun sw ->
let flow = Dir.open_in ~sw dir "myfile.txt" in
...
);
(* [flow] will have been closed by this point *)
]}
*)
type t
(** A switch contains a group of fibers and other resources (such as open file handles). *)
(** {2 Switch creation} *)
val run : ?name:string -> (t -> 'a) -> 'a
(** [run fn] runs [fn] with a fresh switch (initially on).
When [fn] finishes, [run] waits for all fibers registered with the switch to finish,
and then releases all attached resources.
If {!fail} is called, [run] will re-raise the exception (after everything is cleaned up).
If [fn] raises an exception, it is passed to {!fail}.
@param name Used to name the switch when tracing. *)
val run_protected : ?name:string -> (t -> 'a) -> 'a
(** [run_protected fn] is like [run] but ignores cancellation requests from the parent context. *)
(** {2 Cancellation and failure} *)
val check : t -> unit
(** [check t] checks that [t] is still on.
@raise Cancel.Cancelled If the switch has been cancelled. *)
val get_error : t -> exn option
(** [get_error t] is like [check t] except that it returns the exception instead of raising it.
If [t] is finished, this returns (rather than raising) the [Invalid_argument] exception too. *)
val fail : ?bt:Printexc.raw_backtrace -> t -> exn -> unit
(** [fail t ex] adds [ex] to [t]'s set of failures and
ensures that the switch's cancellation context is cancelled,
to encourage all fibers to exit as soon as possible.
[fail] returns immediately, without waiting for the shutdown actions to complete.
The exception will be raised later by {!run}, and [run]'s caller is responsible for handling it.
{!Exn.combine} is used to avoid duplicate or unnecessary exceptions.
@param bt A backtrace to attach to [ex] *)
(** {2 Cleaning up resources}
It is possible to attach clean-up hooks to a switch.
Once all fibers within the switch have finished, these hooks are called.
For example, when a file is opened it will register a release hook to close it.
Functions that create such resources will take a switch argument
and call these functions for you.
You usually don't need to call these directly. *)
val on_release : t -> (unit -> unit) -> unit
(** [on_release t fn] registers [fn] to be called once [t]'s main function has returned
and all fibers have finished.
If [fn] raises an exception, it is passed to {!fail}.
Release handlers are run in LIFO order, in series.
Note that [fn] is called within a {!Cancel.protect}, since aborting clean-up actions is usually a bad idea
and the switch may have been cancelled by the time it runs.
You cannot attach new resources to a switch once the cancel hooks start to run.
This function is thread-safe (but not signal-safe).
If the switch finishes before [fn] can be registered,
it raises [Invalid_argument] and runs [fn] immediately instead. *)
type hook
(** A handle for removing a clean-up callback. *)
val null_hook : hook
(** A dummy hook. [try_remove_hook null_hook = false]. *)
val on_release_cancellable : t -> (unit -> unit) -> hook
(** Like [on_release], but the handler can be removed later.
For example, opening a file will call [on_release_cancellable] to ensure the file is closed later.
However, if the file is manually closed before that, it will use {!remove_hook} to remove the hook,
which is no longer needed.
This function is thread-safe (but not signal-safe). *)
val try_remove_hook : hook -> bool
(** [try_remove_hook h] removes a previously-added hook.
Returns [true] if the hook was successfully removed, or [false] if another
domain ran it or removed it first.
This function is thread-safe (but not signal-safe). *)
val remove_hook : hook -> unit
(** [remove_hook h] is [ignore (try_remove_hook h)].
For multi-domain code, consider using {!try_remove_hook} instead
so that you can handle the case of trying to close a resource
just as another domain is closing it or finishing the switch. *)
(** {2 Debugging} *)
val dump : t Fmt.t
(** Dump out details of the switch's state for debugging. *)
end
(** @canonical Eio.Promise *)
module Promise : sig
(** Unlike lazy values, you cannot "force" promises;
a promise is resolved when the maker of the promise is ready.
Promises are thread-safe and so can be shared between domains and used
to communicate between them.
Example:
{[
let promise, resolver = Promise.create () in
Fiber.both
(fun () -> traceln "Got %d" (Promise.await promise))
(fun () -> Promise.resolve resolver 42)
]} *)
type +!'a t
(** An ['a t] is a promise for a value of type ['a]. *)
type -!'a u
(** An ['a u] is a resolver for a promise of type ['a]. *)
val create : ?label:string -> unit -> 'a t * 'a u
(** [create ()] is a fresh promise/resolver pair.
The promise is initially unresolved. *)
val create_resolved : 'a -> 'a t
(** [create_resolved x] is a promise that is already resolved with result [x]. *)
val await : 'a t -> 'a
(** [await t] blocks until [t] is resolved.
If [t] is already resolved then this returns immediately. *)
val resolve : 'a u -> 'a -> unit
(** [resolve u v] resolves [u]'s promise with the value [v].
Any threads waiting for the result will be added to the run queue.
@raise Invalid_argument if [u] is already resolved. *)
val try_resolve : 'a u -> 'a -> bool
(** [try_resolve] is like {!resolve} but returns [false] instead of raising [Invalid_argument].
Returns [true] on success. *)
val peek : 'a t -> 'a option
(** [peek t] is [Some v] if the promise has been resolved to [v], or [None] otherwise.
If the result is [None] then it may change in future, otherwise it won't.
If another domain has access to the resolver then the state may have already
changed by the time this call returns. *)
val is_resolved : 'a t -> bool
(** [is_resolved t] is [Option.is_some (peek t)]. *)
(** {1 Result promises} *)
type 'a or_exn = ('a, exn) result t
val resolve_ok : ('a, 'b) result u -> 'a -> unit
(** [resolve_ok u x] is [resolve u (Ok x)]. *)
val resolve_error : ('a, 'b) result u -> 'b -> unit
(** [resolve_error u x] is [resolve u (Error x)]. *)
val await_exn : 'a or_exn -> 'a
(** [await_exn t] is like [await t], but if the result is [Error ex] then it raises [ex]. *)
end
(** @canonical Eio.Fiber *)
module Fiber : sig
(** Within a domain, only one fiber can be running at a time.
A fiber runs until it performs an IO operation (directly or indirectly).
At that point, it may be suspended and the next fiber on the run queue runs. *)
val both : (unit -> unit) -> (unit -> unit) -> unit
(** [both f g] runs [f ()] and [g ()] concurrently.
They run in a new cancellation sub-context, and
if either raises an exception, the other is cancelled.
[both] waits for both functions to finish even if one raises
(it will then re-raise the original exception).
[f] runs immediately, without switching to any other thread.
[g] is inserted at the head of the run-queue, so it runs next even if other threads are already enqueued.
You can get other scheduling orders by adding calls to {!yield} in various places.
e.g. to append both fibers to the end of the run-queue, yield immediately before calling [both].
If both fibers fail, {!Exn.combine} is used to combine the exceptions. *)
val pair : (unit -> 'a) -> (unit -> 'b) -> 'a * 'b
(** [pair f g] is like [both], but returns the two results. *)
val all : (unit -> unit) list -> unit
(** [all fs] is like [both], but for any number of fibers.
[all []] returns immediately. *)
val first : ?combine:('a -> 'a -> 'a) -> (unit -> 'a) -> (unit -> 'a) -> 'a
(** [first f g] runs [f ()] and [g ()] concurrently.
They run in a new cancellation sub-context, and when one finishes the other is cancelled.
If one raises, the other is cancelled and the exception is reported.
As with [both], [f] runs immediately and [g] is scheduled next, ahead of any other queued work.
If both fibers fail, {!Exn.combine} is used to combine the exceptions.
Warning: it is always possible that {i both} operations will succeed.
This is because there is a period of time after the first operation succeeds
when it is waiting in the run-queue to resume
during which the other operation may also succeed.
If both fibers succeed, [combine a b] is used to combine the results
(where [a] is the result of the first fiber to return and [b] is the second result).
The default is [fun a _ -> a], which discards the later result. *)
val any : ?combine:('a -> 'a -> 'a) -> (unit -> 'a) list -> 'a
(** [any fs] is like [first], but for any number of fibers.
[any []] just waits forever (or until cancelled). *)
val n_any : (unit -> 'a) list -> 'a list
(** [n_any fs] is like [any], expect that if multiple fibers return values
then they are all returned, in the order in which the fibers finished. *)
val await_cancel : unit -> 'a
(** [await_cancel ()] waits until cancelled.
@raise Cancel.Cancelled *)
val fork : sw:Switch.t -> (unit -> unit) -> unit
(** [fork ~sw fn] runs [fn ()] in a new fiber, but does not wait for it to complete.
The new fiber is attached to [sw] (which can't finish until the fiber ends).
The new fiber inherits [sw]'s cancellation context.
If the fiber raises an exception, [Switch.fail sw] is called.
If [sw] is already off then [fn] fails immediately, but the calling thread continues.
[fn] runs immediately, without switching to any other fiber first.
The calling fiber is placed at the head of the run queue, ahead of any previous items. *)
val fork_promise : sw:Switch.t -> (unit -> 'a) -> 'a Promise.or_exn
(** [fork_promise ~sw fn] schedules [fn ()] to run in a new fiber and returns a promise for its result.
This is just a convenience wrapper around {!fork}.
If [fn] raises an exception then the promise is resolved to the error, but [sw] is not failed. *)
val fork_seq : sw:Switch.t -> (('a -> unit) -> unit) -> 'a Seq.t
(** [fork_seq ~sw fn] creates (but does not start) a new fiber to run [fn yield].
Requesting the next item from the returned sequence resumes the fiber until it
calls [yield x], using [x] value as the next item in the sequence. If [fn]
returns without producing a value then the result is {!Seq.Nil} (end-of-sequence).
The returned sequence can be consumed safely from another domain.
[fn] itself always runs in the domain that called [fork_seq].
Example:
{[
Switch.run @@ fun sw ->
let seq = Fiber.fork_seq ~sw (fun yield ->
for i = 1 to 3 do
traceln "Yielding %d" i;
yield i
done
) in
Seq.iter (traceln "Got: %d") seq
]}
If [fn] raises an exception then the consumer receives it.
If the consumer cancels while awaiting a value, the producer is cancelled when
it next calls [yield].
It is an error to request two items at once, or to request items out of sequence.
@param sw When the switch finishes, the fiber is cancelled (if still running).
Attempting to read from the sequence after this raises an exception. *)
val fork_daemon : sw:Switch.t -> (unit -> [`Stop_daemon]) -> unit
(** [fork_daemon] is like {!fork} except that instead of waiting for the fiber to finish,
the switch will cancel it once all non-daemon fibers are done.
The switch will still wait for the daemon fiber to finish cancelling.
The return type of [[`Stop_daemon]] instead of [unit] is just to catch mistakes,
as daemons normally aren't expected to return. *)
val check : unit -> unit
(** [check ()] checks that the fiber's context hasn't been cancelled.
Many operations automatically check this before starting.
@raise Cancel.Cancelled if the fiber's context has been cancelled. *)
val is_cancelled : unit -> bool
(** [is_cancelled ()] is [true] iff {!check} would raise an exception. *)
val yield : unit -> unit
(** [yield ()] asks the scheduler to switch to the next runnable task.
The current task remains runnable, but goes to the back of the queue.
Automatically calls {!check} just before resuming. *)
(** Concurrent list operations. *)
module List : sig
(** These functions behave like the ones in the standard library's [List]
module, except that multiple items can be processed concurrently.
They correspond to Lwt's [Lwt_list.*_p] operations. e.g.
[Lwt_list.iter_p] becomes [Fiber.List.iter].
For the [Lwt_list.*_s] operations, just use the standard library function.
e.g. [Lwt_list.iter_s] can be replaced by a plain [List.iter]. *)
val filter : ?max_fibers:int -> ('a -> bool) -> 'a list -> 'a list
(** [filter f x] is like [List.filter f x] except that the invocations of [f] are
run concurrently in separate fibers.
@param max_fibers Maximum number of fibers to run concurrently *)
val map : ?max_fibers:int -> ('a -> 'b) -> 'a list -> 'b list
(** [map f x] is like [List.map f x] except that the invocations of [f] are
run concurrently in separate fibers.
@param max_fibers Maximum number of fibers to run concurrently *)
val filter_map : ?max_fibers:int -> ('a -> 'b option) -> 'a list -> 'b list
(** [filter_map f x] is like [List.filter_map f x] except that the
invocations of [f] are run concurrently in separate fibers.
@param max_fibers Maximum number of fibers to run concurrently *)
val iter : ?max_fibers:int -> ('a -> unit) -> 'a list -> unit
(** [iter f x] is like [List.iter f x] except that the invocations of [f] are
run concurrently in separate fibers.
@param max_fibers Maximum number of fibers to run concurrently *)
end
(** {2 Fiber-local variables}
Each fiber maintains a map of additional variables associated with it,
which can be used to store fiber-related state or context. This map is
propagated to any forked fibers.
While fiber-local variables can be useful, they can also make code much
harder to reason about, as they effectively act as another form of global
state. When possible, prefer passing arguments around explicitly.
Fiber-local variables are particularly useful for attaching extra
information for debugging, such as a request ID that the log system can
include in all logged messages.
*)
type 'a key
(** ['a key] is a fiber-local variable of type ['a].
Since the key is required to get or set a variable, a library can keep its
key private to control how the variable can be accessed. *)
val create_key : unit -> 'a key
(** [create_key ()] creates a new fiber-local variable. *)
val get : 'a key -> 'a option
(** [get key] reads [key] from the map of fiber local variables, returning its
value or {!None} if it has not been bound. *)
val with_binding : 'a key -> 'a -> (unit -> 'b) -> 'b
(** [with_binding key value fn] runs [fn] with [key] bound to the provided
[value].
Whilst this binding only exists for the duration of this function {i on
this fiber}, it will be propagated to any forked fibers. If [fn] creates
fibers using an external switch, the bound value may be continue to be
used after this function returns. *)
val without_binding : 'a key -> (unit -> 'b) -> 'b
(** [with_binding key value fn] runs [fn] with any binding for [key] removed.
*)
end
(** @canonical Eio.Exn *)
module Exn : sig
type with_bt = exn * Printexc.raw_backtrace
type err = ..
(** Describes the particular error that occurred.
They are typically nested (e.g. [Fs (Permission_denied (Unix_error ...))])
so that you can match e.g. all IO errors, all file-system errors, all
permission denied errors, etc.
If you extend this, use {!register_pp} to add a printer for the new error. *)
type context
(** Extra information attached to an IO error.
This provides contextual information about what caused the error. *)
exception Io of err * context
(** A general purpose IO exception.
This is used for most errors interacting with the outside world,
and is similar to {!Unix.Unix_error}, but more general.
An unknown [Io] error should typically be reported to the user, but does
not generally indicate a bug in the program. *)
type err += Multiple_io of (err * context * Printexc.raw_backtrace) list
(** Error code used when multiple IO errors occur.
This is useful if you want to catch and report all IO errors. *)
val create : err -> exn
(** [create err] is an {!Io} exception with an empty context. *)
val add_context : exn -> ('a, Format.formatter, unit, exn) format4 -> 'a
(** [add_context ex msg] returns a new exception with [msg] added to [ex]'s context,
if [ex] is an {!Io} exception.
If [ex] is not an [Io] exception, this function just returns the original exception. *)
val reraise_with_context : exn -> Printexc.raw_backtrace -> ('a, Format.formatter, unit, 'b) format4 -> 'a
(** [reraise_with_context ex bt msg] raises [ex] extended with additional information [msg].
[ex] should be an {!Io} exception (if not, is re-raised unmodified).
Example:
{[
try connect addr
with Eio.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in
reraise_with_context ex bt "connecting to %S" addr
]}
You must get the backtrace before calling any other function
in the exception handler to prevent corruption of the backtrace. *)
val register_pp : (Format.formatter -> err -> bool) -> unit
(** [register_pp pp] adds [pp] as a pretty-printer of errors.
[pp f err] should format [err] using [f], if possible.
It should return [true] on success, or [false] if it didn't
recognise [err]. *)
val pp : exn Fmt.t
(** [pp] is a formatter for exceptions.
This is similar to {!Fmt.exn}, but can do a better job on {!Io} exceptions
because it can format them directly without having to convert to a string first. *)
val pp_err : err Fmt.t
(** [pp_err] formats an error code. *)
val empty_backtrace : Printexc.raw_backtrace
(** A backtrace with no frames. *)
(** Extensible backend-specific exceptions. *)
module Backend : sig
type t = ..
val show : bool ref
(** Controls the behaviour of {!pp}. *)
val register_pp : (Format.formatter -> t -> bool) -> unit
(** [register_pp pp] adds [pp] as a pretty-printer of backend errors.
[pp f err] should format [err] using [f], if possible.
It should return [true] on success, or [false] if it didn't
recognise [err]. *)
val pp : t Fmt.t
(** [pp] behaves like {!pp} except that if display of backend errors has been turned off
(with {!show}) then it just prints a place-holder.
This is useful for formatting the backend-specific part of exceptions,
which should be hidden in expect-style testing that needs to work on multiple backends. *)
end
type err += X of Backend.t
(** A top-level code for backend errors that don't yet have a cross-platform classification in Eio.
You should avoid matching on these (in portable code). Instead, request a proper Eio code for them. *)
exception Multiple of with_bt list
(** Raised if multiple fibers fail, to report all the exceptions.
This usually indicates a bug in the program.
Note: If multiple {b IO} errors occur, then you will get [Io (Multiple_io _, _)] instead of this. *)
val combine : with_bt -> with_bt -> with_bt
(** [combine x y] returns a single exception and backtrace to use to represent two errors.
The resulting exception is typically just [Multiple [y; x]],
but various heuristics are used to simplify the result:
- Combining with a {!Cancel.Cancelled} exception does nothing, as these don't need to be reported.
The result is only [Cancelled] if there is no other exception available.
- If both errors are [Io] errors, then the result is [Io (Multiple_io _)]. *)
end
(** @canonical Eio.Cancel *)
module Cancel : sig
(** This is the low-level interface to cancellation.
Every {!Switch} includes a cancellation context and most users will just use that API instead.
Each domain has a tree of cancellation contexts, and every fiber is registered with one context.
A fiber can switch to a different context (e.g. by calling {!sub}).
When a context is cancelled, all registered fibers have their current cancellation function (if any)
called and removed. Child contexts are cancelled too, recursively, unless marked as protected.
Many operations also check that the current context hasn't been cancelled,
so if a fiber is performing a non-cancellable operation it will still get cancelled soon afterwards.
This check is typically done when starting an operation, not at the end.
If an operation is cancelled after succeeding, but while still waiting on the run queue,
it will still return the operation's result.
A notable exception is {!Fiber.yield}, which checks at the end.
You can also use {!Fiber.check} to check manually.
Whether a fiber is cancelled through a cancellation function or by checking its context,
it will receive a {!Cancelled} exception.
It is possible the exception will get lost (if something catches it and forgets to re-raise).
It is also possible to get this exception even when not cancelled, for example by awaiting
a promise which another fiber has resolved to a cancelled exception.
When in doubt, use [Fiber.check ()] to find out if your fiber is really cancelled.
Ideally this should be done any time you have caught an exception and are planning to ignore it,
although if you forget then the next IO operation will typically abort anyway.
When handling a [Cancelled] exception, quick clean-up actions
(such as releasing a mutex or deleting a temporary file) are OK,
but operations that may block should be avoided.
For example, a network connection should simply be closed,
without attempting to send a goodbye message.
The purpose of the cancellation system is to stop fibers quickly, not to report errors.
Use {!Switch.fail} instead to record an error. *)
type t
(** A cancellation context. *)
exception Cancelled of exn
(** [Cancelled ex] indicates that the context was cancelled with exception [ex].
It is usually not necessary to report a [Cancelled] exception to the user,
as the original problem will be handled elsewhere.
The nested exception is only intended for debug-level logging and should generally be ignored. *)
val sub : (t -> 'a) -> 'a
(** [sub fn] installs a new cancellation context [t], runs [fn t] inside it, and then restores the old context.
If the old context is cancelled while [fn] is running then [t] is cancelled too.
[t] cannot be used after [sub] returns. *)
val protect : (unit -> 'a) -> 'a
(** [protect fn] runs [fn] in a new cancellation context that isn't cancelled when its parent is.
This can be used to clean up resources on cancellation.
However, it is usually better to use {!Switch.on_release} (which calls this for you).
Note that [protect] does not check its parent context when it finishes. *)
val check : t -> unit
(** [check t] checks that [t] hasn't been cancelled.
@raise Cancelled If the context has been cancelled. *)
val get_error : t -> exn option
(** [get_error t] is like [check t] except that it returns the exception instead of raising it.
If [t] is finished, this returns (rather than raising) the [Invalid_argument] exception too. *)
val cancel : t -> exn -> unit
(** [cancel t ex] marks [t] and its child contexts as cancelled, recursively,
and calls all registered fibers' cancellation functions, passing [Cancelled ex] as the argument.
All cancellation functions are run, even if some of them raise exceptions.
If [t] is already cancelled then this does nothing.
Note that the caller of this function is still responsible for handling the error somehow
(e.g. reporting it to the user); it does not become the responsibility of the cancelled thread(s). *)
val dump : t Fmt.t
(** Show the cancellation sub-tree rooted at [t], for debugging. *)
end
(** @canonical Eio.Private *)
module Private : sig
module Trace = Trace
module Cells = Cells
module Broadcast = Broadcast
module Single_waiter = Single_waiter
(** Every fiber has an associated context. *)
module Fiber_context : sig
type t
val make_root : unit -> t
(** Make a new root context for a new domain. *)
val destroy : t -> unit
(** [destroy t] removes [t] from its cancellation context. *)
val tid : t -> Trace.id
(** {2 Cancellation}
The {!Cancel} module describes the user's view of cancellation.
Internally, when the user calls a primitive operation that needs to block the fiber,
the [Suspend callback] effect is performed.
This suspends the fiber and calls [callback] from the scheduler's context,
passing it the suspended fiber's context.
If the operation can be cancelled,
the callback should use {!set_cancel_fn} to register a cancellation function.
There are two possible outcomes for the operation: it may complete normally,
or it may be cancelled.
If it is cancelled then the registered cancellation function is called.
This function will always be called from the fiber's own domain, but care must be taken
if the operation could be completed by another domain at the same time.
Consider the case of {!Stream.take}, which can be fulfilled by a {!Stream.add} from another domain.
We want to ensure that either the item is removed from the stream and returned to the waiting fiber,
or that the operation is cancelled and the item is not removed from the stream.
Therefore, cancelling and completing both need to update an atomic value (with {!Atomic.compare_and_set})
so that only one can succeed. The case where [Stream.take] succeeds before cancellation:
+ A fiber calls [Suspend] and is suspended.
The callback sets a cancel function and registers a waiter on the stream.
+ When another domain has an item, it marks the atomic as finished (making the [take] uncancellable)
and begins resuming the fiber with the new item.
+ If the taking fiber is cancelled after this, the cancellation must be ignored and the operation
will complete successfully. Future operations will fail immediately, however.
The case of cancellation winning the race:
+ A fiber calls [Suspend] and is suspended.
The callback sets a cancel function and registers a waiter on the stream.
+ The taking fiber is cancelled. Its cancellation function is called,
which updates the atomic and starts removing the waiter.
+ If another domain tries to provide an item to the waiter as this is happening,
it will try to update the atomic too and fail.
The item will be given to the next waiter instead.
Note: A fiber will only have a cancel function set while it is suspended. *)
val cancellation_context : t -> Cancel.t
(** [cancellation_context t] is [t]'s current cancellation context. *)
val set_cancel_fn : t -> (exn -> unit) -> unit
(** [set_cancel_fn t fn] sets [fn] as the fiber's cancel function.
If [t]'s cancellation context is cancelled, the function is called.
It should attempt to make the current operation finish quickly, either with
a successful result or by raising the given exception.
Just before being called, the fiber's cancel function is replaced with [ignore]
so that [fn] cannot be called twice.
On success, the cancel function is cleared automatically when {!Suspend.enter} returns,
but for single-domain operations you may like to call {!clear_cancel_fn}
manually to remove it earlier.
[fn] will be called from [t]'s domain (from the fiber that called [cancel]).
[fn] must not switch fibers. If it did, this could happen:
+ Another suspended fiber in the same cancellation context resumes before
its cancel function is called.
+ It enters a protected block and starts a new operation.
+ [fn] returns.
+ We cancel the protected operation. *)
val clear_cancel_fn : t -> unit
(** [clear_cancel_fn t] is [set_cancel_fn t ignore].
This must only be called from the fiber's own domain.
For single-domain operations, it can be useful to call this manually as soon as
the operation succeeds (i.e. when the fiber is added to the run-queue)
to prevent the cancel function from being called.
For operations where another domain may resume the fiber, your cancel function
will need to cope with being called after the operation has succeeded. In that
case you should not call [clear_cancel_fn]. The backend will do it automatically
just before resuming your fiber. *)
val get_error : t -> exn option
(** [get_error t] is [Cancel.get_error (cancellation_context t)] *)
end
module Effects : sig
type 'a enqueue = ('a, exn) result -> unit
(** A function provided by the scheduler to reschedule a previously-suspended thread. *)
type _ Effect.t +=
| Suspend : (Fiber_context.t -> 'a enqueue -> unit) -> 'a Effect.t
(** [Suspend fn] is performed when a fiber must be suspended
(e.g. because it called {!Promise.await} on an unresolved promise).
The effect handler runs [fn fiber enqueue] in the scheduler context,
passing it the suspended fiber's context and a function to resume it.
[fn] should arrange for [enqueue] to be called once the thread is ready to run again. *)
| Fork : Fiber_context.t * (unit -> unit) -> unit Effect.t
(** [perform (Fork new_context f)] creates a new fiber and runs [f] in it, with context [new_context].
[f] must not raise an exception. See {!Fiber.fork}. *)
| Get_context : Fiber_context.t Effect.t
(** [perform Get_context] immediately returns the current fiber's context (without switching fibers). *)
end
(** Suspend a fiber and enter the scheduler. *)
module Suspend : sig
val enter : string -> (Fiber_context.t -> 'a Effects.enqueue -> unit) -> 'a
(** [enter op fn] suspends the calling fiber and calls [fn ctx enqueue] in the scheduler's context.
This should arrange for [enqueue] to be called when the fiber should be resumed.
[enqueue] is thread-safe and so can be called from another domain or systhread.
[ctx] should be used to set a cancellation function. Otherwise, the operation is non-interruptable.
If the caller's cancellation context is already cancelled, [enter] immediately aborts.
[op] is used when tracing to label the operation. *)
val enter_unchecked : string -> (Fiber_context.t -> 'a Effects.enqueue -> unit) -> 'a
(** [enter_unchecked] is like [enter] except that it does not perform the initial check
that the fiber isn't cancelled (this is useful if you want to do the check yourself, e.g.
because you need to unlock a mutex if cancelled). *)
end
module Debug : sig
val traceln :
?__POS__:string * int * int * int ->
('a, Format.formatter, unit, unit) format4 -> 'a
(** Writes trace logging using the current fiber's configured traceln function. *)
val with_trace_prefix : (Format.formatter -> unit) -> (unit -> 'a) -> 'a
(** [with_trace_prefix fmt fn] runs [fn ()] with a traceln that outputs [fmt] before each message. *)
val traceln_mutex : Stdlib.Mutex.t
(** The mutex used to prevent two domains writing to stderr at once.
This might be useful if you want to write to it directly yourself,
e.g. for a log reporter. *)
val default_traceln :
?__POS__:string * int * int * int ->
('a, Format.formatter, unit, unit) format4 -> 'a
(** [default_traceln] is a suitable default implementation for {!Eio.Std.traceln}.
It writes output to stderr, prefixing each line with a "+".
If [__POS__] is given, it also displays the file and line number from that.
It uses {!traceln_mutex} so that only one domain's output is written at a time. *)
type traceln = {
traceln : 'a. ?__POS__:string * int * int * int -> ('a, Format.formatter, unit, unit) format4 -> 'a;
} [@@unboxed]
type t = <
traceln : traceln Fiber.key;
>
val v : t
(** Backends should use this for {!Eio.Stdenv.debug}. *)
end
end

132
lib_eio/core/exn.ml Normal file
View File

@ -0,0 +1,132 @@
let show_backend_exceptions = ref true
type with_bt = exn * Printexc.raw_backtrace
type err = ..
type context = {
steps : string list;
}
exception Io of err * context
exception Multiple of (exn * Printexc.raw_backtrace) list (* Note: the last exception in list is the first one reported *)
type err += Multiple_io of (err * context * Printexc.raw_backtrace) list
exception Cancelled of exn
let create err = Io (err, { steps = [] })
let empty_backtrace = Printexc.get_callstack 0
let add_context ex fmt =
fmt |> Fmt.kstr @@ fun msg ->
match ex with
| Io (code, t) -> Io (code, {steps = msg :: t.steps})
| ex -> ex
let reraise_with_context ex bt fmt =
fmt |> Fmt.kstr @@ fun msg ->
match ex with
| Io (code, t) ->
let context = { steps = msg :: t.steps } in
Printexc.raise_with_backtrace (Io (code, context)) bt
| _ ->
Printexc.raise_with_backtrace ex bt
let err_printers : (Format.formatter -> err -> bool) list ref = ref []
let register_pp fn =
err_printers := fn :: !err_printers
let break f _ = Format.pp_print_custom_break f
~fits:(",", 1, "")
~breaks:(",", 2, "")
let pp_err f x =
let rec aux = function
| [] -> Fmt.string f "?"
| pp :: pps -> if not (pp f x) then aux pps
in
aux !err_printers
let pp_with_context f (code, context) =
Fmt.pf f "%a%a" pp_err code
Fmt.(list ~sep:nop (break ++ string)) (List.rev context.steps)
let pp_with_bt f (code, context, bt) =
match String.trim (Printexc.raw_backtrace_to_string bt) with
| "" ->
Fmt.pf f "- @[<hov>%a@]"
pp_with_context (code, context)
| bt ->
Fmt.pf f "- @[<v>%a@,%a@]"
pp_with_context (code, context)
Fmt.lines bt
let pp f = function
| Io (code, t) ->
Fmt.pf f "Eio.Io %a%a"
pp_err code
Fmt.(list ~sep:nop (break ++ string)) (List.rev t.steps)
| ex ->
Fmt.string f (Printexc.to_string ex)
let pp_multiple f exns =
let pp_with_bt f (ex, bt) =
match String.trim (Printexc.raw_backtrace_to_string bt) with
| "" ->
Fmt.pf f "- @[<v>%a@]" pp ex
| bt ->
Fmt.pf f "- @[<v>%a@,%a@]"
pp ex
Fmt.lines bt
in
Fmt.pf f "@[<v>Multiple exceptions:@,%a@]"
(Fmt.(list ~sep:cut) pp_with_bt) (List.rev exns)
let () =
Printexc.register_printer @@ function
| Io _ as ex -> Some (Fmt.str "@[<v>%a@]" pp ex)
| Multiple exns -> Some (Fmt.str "%a" pp_multiple exns)
| Cancelled ex -> Some ("Cancelled: " ^ Printexc.to_string ex)
| _ -> None
let combine e1 e2 =
if fst e1 == fst e2 then e1
else match e1, e2 with
| (Cancelled _, _), e
| e, (Cancelled _, _) -> e (* Don't need to report a cancelled exception if we have something better *)
| (Io (c1, t1), bt1), (Io (c2, t2), bt2) -> create (Multiple_io [(c1, t1, bt1); (c2, t2, bt2)]), empty_backtrace
| (Multiple exs, bt1), e2 -> Multiple (e2 :: exs), bt1
| e1, e2 -> Multiple [e2; e1], empty_backtrace
module Backend = struct
type t = ..
let show = ref true
let printers : (Format.formatter -> t -> bool) list ref = ref []
let register_pp fn =
printers := fn :: !printers
let pp f x =
if !show then (
let rec aux = function
| [] -> Fmt.string f "?"
| pp :: pps -> if not (pp f x) then aux pps
in
aux !printers
) else Fmt.string f "_"
end
type err += X of Backend.t
let () =
register_pp (fun f -> function
| Multiple_io errs -> Fmt.pf f "Multiple_io@\n%a" (Fmt.(list ~sep:cut) pp_with_bt) errs; true
| X ex -> Backend.pp f ex; true
| _ -> false
)

401
lib_eio/core/fiber.ml Normal file
View File

@ -0,0 +1,401 @@
[@@@alert "-unstable"]
type _ Effect.t += Fork : Cancel.fiber_context * (unit -> unit) -> unit Effect.t
let yield () =
let fiber = Suspend.enter "" (fun fiber enqueue -> enqueue (Ok fiber)) in
Cancel.check fiber.cancel_context
(* Note: [f] must not raise an exception, as that would terminate the whole scheduler. *)
let fork_raw new_fiber f =
Effect.perform (Fork (new_fiber, f))
let fork ~sw f =
Switch.check_our_domain sw;
if Cancel.is_on sw.cancel then (
let vars = Cancel.Fiber_context.get_vars () in
let new_fiber = Cancel.Fiber_context.make ~cc:sw.cancel ~vars in
fork_raw new_fiber @@ fun () ->
Switch.with_op sw @@ fun () ->
try
f ()
with ex ->
let bt = Printexc.get_raw_backtrace () in
Switch.fail ~bt sw ex; (* The [with_op] ensures this will succeed *)
) (* else the fiber should report the error to [sw], but [sw] is failed anyway *)
let fork_daemon ~sw f =
Switch.check_our_domain sw;
if Cancel.is_on sw.cancel then (
let vars = Cancel.Fiber_context.get_vars () in
let new_fiber = Cancel.Fiber_context.make ~cc:sw.cancel ~vars in
fork_raw new_fiber @@ fun () ->
Switch.with_daemon sw @@ fun () ->
match f () with
| `Stop_daemon ->
(* The daemon asked to stop. *)
()
| exception Cancel.Cancelled Exit when not (Cancel.is_on sw.cancel) ->
(* The daemon was cancelled because all non-daemon fibers are finished. *)
()
| exception ex ->
let bt = Printexc.get_raw_backtrace () in
Switch.fail ~bt sw ex; (* The [with_daemon] ensures this will succeed *)
) (* else the fiber should report the error to [sw], but [sw] is failed anyway *)
let fork_promise ~sw f =
Switch.check_our_domain sw;
let vars = Cancel.Fiber_context.get_vars () in
let new_fiber = Cancel.Fiber_context.make ~cc:sw.Switch.cancel ~vars in
let p, r = Promise.create_with_id (Cancel.Fiber_context.tid new_fiber) in
fork_raw new_fiber (fun () ->
match Switch.with_op sw f with
| x -> Promise.resolve_ok r x
| exception ex -> Promise.resolve_error r ex (* Can't fail; only we have [r] *)
);
p
(* This is not exposed. On failure it fails [sw], but you need to make sure that
any fibers waiting on the promise will be cancelled. *)
let fork_promise_exn ~sw f =
Switch.check_our_domain sw;
let vars = Cancel.Fiber_context.get_vars () in
let new_fiber = Cancel.Fiber_context.make ~cc:sw.Switch.cancel ~vars in
let p, r = Promise.create_with_id (Cancel.Fiber_context.tid new_fiber) in
fork_raw new_fiber (fun () ->
match Switch.with_op sw f with
| x -> Promise.resolve r x
| exception ex ->
let bt = Printexc.get_raw_backtrace () in
Switch.fail ~bt sw ex (* The [with_op] ensures this will succeed *)
);
p
(* Like [List.iter (fork ~sw)], but runs the last one in the current fiber
for efficiency and less cluttered traces. *)
let rec forks ~sw = function
| [] -> ()
| [x] -> Switch.check sw; x ()
| x :: xs ->
fork ~sw x;
forks ~sw xs
let all xs =
Switch.run ~name:"all" @@ fun sw ->
forks ~sw xs
let both f g =
Switch.run ~name:"both" @@ fun sw ->
forks ~sw [f; g]
let pair f g =
Switch.run ~name:"pair" @@ fun sw ->
let x = fork_promise ~sw f in
let y = g () in
(Promise.await_exn x, y)
exception Not_first
let await_cancel () =
Suspend.enter "await_cancel" @@ fun fiber enqueue ->
Cancel.Fiber_context.set_cancel_fn fiber (fun ex -> enqueue (Error ex))
type 'a any_status =
| New
| Ex of (exn * Printexc.raw_backtrace)
| OK of 'a
let any_gen ~return ~combine fs =
let r = ref New in
let parent_c =
Cancel.sub_unchecked Any (fun cc ->
let wrap h =
match h () with
| x ->
begin match !r with
| New -> r := OK (return x); Cancel.cancel cc Not_first
| OK prev -> r := OK (combine prev x)
| Ex _ -> ()
end
| exception Cancel.Cancelled _ when not (Cancel.is_on cc) ->
(* If this is in response to us asking the fiber to cancel then we can just ignore it.
If it's in response to our parent context being cancelled (which also cancels [cc]) then
we'll check that context and raise it at the end anyway. *)
()
| exception ex ->
begin match !r with
| New -> r := Ex (ex, Printexc.get_raw_backtrace ()); Cancel.cancel cc ex
| OK _ -> r := Ex (ex, Printexc.get_raw_backtrace ())
| Ex prev ->
let bt = Printexc.get_raw_backtrace () in
r := Ex (Exn.combine prev (ex, bt))
end
in
let vars = Cancel.Fiber_context.get_vars () in
let rec aux = function
| [] -> await_cancel ()
| [f] -> wrap f; []
| f :: fs ->
let new_fiber = Cancel.Fiber_context.make ~cc ~vars in
let p, r = Promise.create_with_id (Cancel.Fiber_context.tid new_fiber) in
fork_raw new_fiber (fun () ->
match wrap f with
| () -> Promise.resolve_ok r ()
| exception ex -> Promise.resolve_error r ex
);
p :: aux fs
in
let ps = aux fs in
Cancel.protect (fun () -> List.iter Promise.await_exn ps)
)
in
match !r, Cancel.get_error parent_c with
| OK r, None -> r
| (OK _ | New), Some ex -> raise ex
| Ex (ex, bt), None -> Printexc.raise_with_backtrace ex bt
| Ex ex1, Some ex2 ->
let bt2 = Printexc.get_raw_backtrace () in
let ex, bt = Exn.combine ex1 (ex2, bt2) in
Printexc.raise_with_backtrace ex bt
| New, None -> assert false
let n_any fs =
List.rev (any_gen fs ~return:(fun x -> [x]) ~combine:(fun xs x -> x :: xs))
let any ?(combine=(fun x _ -> x)) fs = any_gen fs ~return:Fun.id ~combine
let first ?combine f g = any ?combine [f; g]
let is_cancelled () =
let ctx = Effect.perform Cancel.Get_context in
not (Cancel.is_on ctx.cancel_context)
let check () =
let ctx = Effect.perform Cancel.Get_context in
Cancel.check ctx.cancel_context
(* Some concurrent list operations *)
module List = struct
let opt_cons x xs =
match x with
| None -> xs
| Some x -> x :: xs
module Limiter : sig
(** This is a bit like using a semaphore, but it assumes that there is only a
single fiber using it. e.g. you must not call {!use}, {!fork}, etc from
two different fibers. *)
type t
val create : sw:Switch.t -> int -> t
(** [create ~sw n] is a limiter that allows running up to [n] jobs at once. *)
val use : t -> ('a -> 'b) -> 'a -> 'b
(** [use t fn x] runs [fn x] in this fiber, counting it as one use of [t]. *)
val fork : t -> ('a -> unit) -> 'a -> unit
(** [fork t fn x] runs [fn x] in a new fibre, once a fiber is free. *)
val fork_promise_exn : t -> ('a -> 'b) -> 'a -> 'b Promise.t
(** [fork_promise_exn t fn x] runs [fn x] in a new fibre, once a fiber is free,
and returns a promise for the result. *)
end = struct
type t = {
mutable free_fibers : int;
cond : unit Single_waiter.t;
sw : Switch.t;
}
let max_fibers_err n =
Fmt.failwith "max_fibers must be positive (got %d)" n
let create ~sw max_fibers =
if max_fibers <= 0 then max_fibers_err max_fibers;
{
free_fibers = max_fibers;
cond = Single_waiter.create ();
sw;
}
let await_free t =
if t.free_fibers = 0 then Single_waiter.await t.cond "Limiter.await_free" t.sw.cancel.id;
(* If we got woken up then there was a free fiber then. And since we're the
only fiber that uses [t], and we were sleeping, it must still be free. *)
assert (t.free_fibers > 0);
t.free_fibers <- t.free_fibers - 1
let release t =
t.free_fibers <- t.free_fibers + 1;
if t.free_fibers = 1 then Single_waiter.wake_if_sleeping t.cond
let use t fn x =
await_free t;
let r = fn x in
release t;
r
let fork_promise_exn t fn x =
await_free t;
fork_promise_exn ~sw:t.sw (fun () -> let r = fn x in release t; r)
let fork t fn x =
await_free t;
fork ~sw:t.sw (fun () -> fn x; release t)
end
let filter_map ?(max_fibers=max_int) fn items =
match items with
| [] -> [] (* Avoid creating a switch in the simple case *)
| items ->
Switch.run ~name:"filter_map" @@ fun sw ->
let limiter = Limiter.create ~sw max_fibers in
let rec aux = function
| [] -> []
| [x] -> Option.to_list (Limiter.use limiter fn x)
| x :: xs ->
let x = Limiter.fork_promise_exn limiter fn x in
let xs = aux xs in
opt_cons (Promise.await x) xs
in
aux items
let map ?max_fibers fn = filter_map ?max_fibers (fun x -> Some (fn x))
let filter ?max_fibers fn = filter_map ?max_fibers (fun x -> if fn x then Some x else None)
let iter ?(max_fibers=max_int) fn items =
match items with
| [] -> () (* Avoid creating a switch in the simple case *)
| items ->
Switch.run ~name:"iter" @@ fun sw ->
let limiter = Limiter.create ~sw max_fibers in
let rec aux = function
| [] -> ()
| [x] -> Limiter.use limiter fn x
| x :: xs ->
Limiter.fork limiter fn x;
aux xs
in
aux items
end
type 'a key = 'a Hmap.key
let create_key () = Hmap.Key.create ()
let get key = Hmap.find key (Cancel.Fiber_context.get_vars ())
let with_binding var value fn =
let ctx = Effect.perform Cancel.Get_context in
Cancel.Fiber_context.with_vars ctx (Hmap.add var value ctx.vars) fn
let without_binding var fn =
let ctx = Effect.perform Cancel.Get_context in
Cancel.Fiber_context.with_vars ctx (Hmap.rem var ctx.vars) fn
(* Coroutines.
[fork_coroutine ~sw fn] creates a new fiber for [fn]. [fn] immediately suspends, setting its state to
[Ready enqueue]. A consumer can resume it by setting the state to [Running] and calling [enqueue],
while suspending itself. The consumer passes in its own [enqueue] function. They run alternatively
like this, switching between the [Ready] and [Running] states.
To finish, the coroutine fiber can set the state to [Finished] or [Failed],
or the client can set the state to [Client_cancelled].
*)
(* Note: we could easily generalise this to [('in, 'out) coroutine] if that was useful. *)
type 'out coroutine =
[ `Init
| `Ready of [`Running of 'out Suspend.enqueue] Suspend.enqueue
| `Running of 'out Suspend.enqueue
| `Finished
| `Client_cancelled of exn
| `Failed of exn ]
(* The only good reason for the state to change while the coroutine is running is if the client
cancels. Return the exception in that case. If the coroutine is buggy it might e.g. fork two
fibers and yield twice for a single request - return Invalid_argument in that case. *)
let unwrap_cancelled state =
match Atomic.get state with
| `Client_cancelled ex -> ex
| `Finished | `Failed _ -> Invalid_argument "Coroutine has already stopped!"
| `Ready _ -> Invalid_argument "Coroutine has already yielded!"
| `Init | `Running _ -> Invalid_argument "Coroutine in unexpected state!"
let run_coroutine ~state fn =
let await_request ~prev ~on_suspend =
(* Suspend and wait for the consumer to resume us: *)
Suspend.enter "await-consumer" (fun ctx enqueue ->
let ready = `Ready enqueue in
if Atomic.compare_and_set state prev ready then (
Cancel.Fiber_context.set_cancel_fn ctx (fun ex ->
if Atomic.compare_and_set state ready (`Failed ex) then
enqueue (Error ex);
(* else the client enqueued a resume for us; handle that instead *)
);
on_suspend ()
) else (
enqueue (Error (unwrap_cancelled state))
)
)
in
let current_state = ref (await_request ~prev:`Init ~on_suspend:ignore) in
fn (fun v ->
(* The coroutine wants to yield the value [v] and suspend. *)
let `Running enqueue as prev = !current_state in
current_state := await_request ~prev ~on_suspend:(fun () -> enqueue (Ok (Some v)))
);
(* [fn] has finished. End the stream. *)
if Atomic.compare_and_set state (!current_state :> _ coroutine) `Finished then (
let `Running enqueue = !current_state in
enqueue (Ok None)
) else (
raise (unwrap_cancelled state)
)
let fork_coroutine ~sw fn =
let state = Atomic.make `Init in
fork_daemon ~sw (fun () ->
try
run_coroutine ~state fn;
`Stop_daemon
with ex ->
match ex, Atomic.exchange state (`Failed ex) with
| _, `Running enqueue ->
(* A client is waiting for us. Send the error there. Also do this if we were cancelled. *)
enqueue (Error ex);
`Stop_daemon
| Cancel.Cancelled _, _ ->
(* The client isn't waiting (probably it got cancelled, then we tried to yield to it and got cancelled too).
If it tries to resume us later it will see the error. *)
`Stop_daemon
| _ ->
(* Something unexpected happened. Re-raise. *)
raise ex
);
fun () ->
Suspend.enter "await-producer" (fun ctx enqueue ->
let rec aux () =
match Atomic.get state with
| `Ready resume as prev ->
let running = `Running enqueue in
if Atomic.compare_and_set state prev running then (
resume (Ok running);
Cancel.Fiber_context.set_cancel_fn ctx (fun ex ->
if Atomic.compare_and_set state running (`Client_cancelled ex) then
enqueue (Error ex)
)
) else aux ()
| `Finished -> enqueue (Error (Invalid_argument "Coroutine has already finished!"))
| `Failed ex | `Client_cancelled ex -> enqueue (Error (Invalid_argument ("Coroutine has already failed: " ^ Printexc.to_string ex)))
| `Running _ -> enqueue (Error (Invalid_argument "Coroutine is still running!"))
| `Init -> assert false
in
aux ()
)
let fork_seq ~sw fn =
Seq.of_dispenser (fork_coroutine ~sw fn)

107
lib_eio/core/promise.ml Normal file
View File

@ -0,0 +1,107 @@
type 'a state =
| Resolved of 'a
| Unresolved of Broadcast.t
type !'a promise = {
id : Trace.id;
state : 'a state Atomic.t; (* Note: we always switch to Resolved before broadcasting *)
}
type +!'a t
type -!'a u
type 'a or_exn = ('a, exn) result t
let to_public_promise : 'a promise -> 'a t = Obj.magic
let to_public_resolver : 'a promise -> 'a u = Obj.magic
let of_public_promise : 'a t -> 'a promise = Obj.magic
let of_public_resolver : 'a u -> 'a promise = Obj.magic
let create_with_id id =
let t = {
id;
state = Atomic.make (Unresolved (Broadcast.create ()));
} in
to_public_promise t, to_public_resolver t
let create ?label () =
let id = Trace.mint_id () in
Trace.create_obj ?label id Promise;
create_with_id id
let create_resolved x =
let id = Trace.mint_id () in
Trace.create_obj id Promise;
to_public_promise { id; state = Atomic.make (Resolved x) }
let await t =
let t = of_public_promise t in
match Atomic.get t.state with
| Resolved x ->
Trace.get t.id;
x
| Unresolved b ->
Suspend.enter "Promise.await" (fun ctx enqueue ->
match Broadcast.suspend b (fun () -> enqueue (Ok ())) with
| None -> () (* We got resumed immediately *)
| Some request ->
match Atomic.get t.state with
| Resolved _ ->
(* The promise was resolved as we were suspending.
Resume now if we haven't already done so. *)
if Broadcast.cancel request then enqueue (Ok ())
| Unresolved _ ->
(* We observed the promise to be still unresolved after registering a waiter.
Therefore any resolution must happen after we were registered and we will be notified. *)
Trace.try_get t.id;
Cancel.Fiber_context.set_cancel_fn ctx (fun ex ->
if Broadcast.cancel request then enqueue (Error ex)
(* else already resumed *)
)
);
match Atomic.get t.state with
| Resolved x ->
Trace.get t.id;
x
| Unresolved _ -> assert false
let await_exn t =
match await t with
| Ok x -> x
| Error ex -> raise ex
let try_resolve t v =
let rec resolve' t v =
match Atomic.get t.state with
| Resolved _ -> false
| Unresolved b as prev ->
if Atomic.compare_and_set t.state prev (Resolved v) then (
Trace.put t.id;
Broadcast.resume_all b;
true
) else (
(* Otherwise, the promise was already resolved. Retry (to get the error). *)
resolve' t v
)
in
resolve' (of_public_resolver t) v
let resolve u x =
if not (try_resolve u x) then
invalid_arg "Can't resolve already-resolved promise"
let resolve_ok u x = resolve u (Ok x)
let resolve_error u x = resolve u (Error x)
let peek t =
let t = of_public_promise t in
match Atomic.get t.state with
| Unresolved _ -> None
| Resolved x -> Some x
let id t =
let t = of_public_promise t in
t.id
let is_resolved t =
Option.is_some (peek t)

View File

@ -0,0 +1,42 @@
type 'a state =
| Running
| Sleeping of (('a, exn) result -> unit)
type 'a t = 'a state ref
let create () = ref Running
let wake t v =
match !t with
| Running -> false
| Sleeping fn ->
t := Running;
fn v;
true
let wake_if_sleeping t =
ignore (wake t (Ok ()) : bool)
let await t op id =
let x =
Suspend.enter op @@ fun ctx enqueue ->
Cancel.Fiber_context.set_cancel_fn ctx (fun ex ->
t := Running;
enqueue (Error ex)
);
t := Sleeping (fun x ->
Cancel.Fiber_context.clear_cancel_fn ctx;
t := Running;
enqueue x
)
in
Trace.get id;
x
let await_protect t op id =
let x =
Suspend.enter_unchecked op @@ fun _ctx enqueue ->
t := Sleeping (fun x -> t := Running; enqueue x)
in
Trace.get id;
x

View File

@ -0,0 +1,25 @@
(** Allows a single fiber to wait to be notified by another fiber in the same domain.
If multiple fibers need to wait at once, or the notification comes from another domain,
this can't be used. *)
type 'a t
(** A handle representing a fiber that might be sleeping.
It is either in the Running or Sleeping state. *)
val create : unit -> 'a t
(** [create ()] is a new waiter, initially in the Running state. *)
val wake : 'a t -> ('a, exn) result -> bool
(** [wake t v] resumes [t]'s fiber with value [v] and returns [true] if it was sleeping.
If [t] is Running then this just returns [false]. *)
val wake_if_sleeping : unit t -> unit
(** [wake_if_sleeping] is [ignore (wake t (Ok ()))]. *)
val await : 'a t -> string -> Trace.id -> 'a
(** [await t op id] suspends the calling fiber, changing [t]'s state to Sleeping.
If the fiber is cancelled, a cancel exception is raised.
[op] and [id] are used for tracing. *)
val await_protect : 'a t -> string -> Trace.id -> 'a
(** [await_protect] is like {!await}, but the sleep cannot be cancelled. *)

View File

@ -1,10 +1,12 @@
type 'a enqueue = ('a, exn) result -> unit type 'a enqueue = ('a, exn) result -> unit
type _ Effect.t += Suspend : (Cancel.fiber_context -> 'a enqueue -> unit) -> 'a Effect.t type _ Effect.t += Suspend : (Cancel.fiber_context -> 'a enqueue -> unit) -> 'a Effect.t
let enter_unchecked fn = Effect.perform (Suspend fn) let enter_unchecked op fn =
Trace.suspend_fiber op;
Effect.perform (Suspend fn)
let enter fn = let enter op fn =
enter_unchecked @@ fun fiber enqueue -> enter_unchecked op @@ fun fiber enqueue ->
match Cancel.Fiber_context.get_error fiber with match Cancel.Fiber_context.get_error fiber with
| None -> fn fiber enqueue | None -> fn fiber enqueue
| Some ex -> enqueue (Error ex) | Some ex -> enqueue (Error ex)

199
lib_eio/core/switch.ml Normal file
View File

@ -0,0 +1,199 @@
type t = {
mutable fibers : int; (* Total, including daemon_fibers and the main function *)
mutable daemon_fibers : int;
mutable exs : (exn * Printexc.raw_backtrace) option;
on_release_lock : Mutex.t;
mutable on_release : (unit -> unit) Lwt_dllist.t option; (* [None] when closed. *)
waiter : unit Single_waiter.t; (* The main [top]/[sub] function may wait here for fibers to finish. *)
cancel : Cancel.t;
}
type hook =
| Null
| Hook : Mutex.t * (unit -> unit) Lwt_dllist.node -> hook
let null_hook = Null
let cancelled () = assert false
let try_remove_hook = function
| Null -> false
| Hook (on_release_lock, n) ->
Mutex.lock on_release_lock;
Lwt_dllist.remove n;
let fn = Lwt_dllist.get n in
Lwt_dllist.set n cancelled;
Mutex.unlock on_release_lock;
fn != cancelled
let remove_hook x = ignore (try_remove_hook x : bool)
let dump f t =
Fmt.pf f "@[<v2>Switch %d (%d extra fibers):@,%a@]"
(t.cancel.id :> int)
t.fibers
Cancel.dump t.cancel
let is_finished t = Cancel.is_finished t.cancel
(* Check switch belongs to this domain (and isn't finished). It's OK if it's cancelling. *)
let check_our_domain t =
if is_finished t then invalid_arg "Switch finished!";
if Domain.self () <> t.cancel.domain then invalid_arg "Switch accessed from wrong domain!"
(* Check isn't cancelled (or finished). *)
let check t =
if is_finished t then invalid_arg "Switch finished!";
Cancel.check t.cancel
let get_error t =
Cancel.get_error t.cancel
let combine_exn ex = function
| None -> ex
| Some ex1 -> Exn.combine ex1 ex
(* Note: raises if [t] is finished or called from wrong domain. *)
let fail ?(bt=Exn.empty_backtrace) t ex =
check_our_domain t;
t.exs <- Some (combine_exn (ex, bt) t.exs);
try
Cancel.cancel t.cancel ex
with ex ->
let bt = Printexc.get_raw_backtrace () in
t.exs <- Some (combine_exn (ex, bt) t.exs)
let inc_fibers t =
check t;
t.fibers <- t.fibers + 1
let dec_fibers t =
t.fibers <- t.fibers - 1;
if t.daemon_fibers > 0 && t.fibers = t.daemon_fibers then
Cancel.cancel t.cancel Exit;
if t.fibers = 0 then
Single_waiter.wake_if_sleeping t.waiter
let with_op t fn =
inc_fibers t;
Fun.protect fn
~finally:(fun () -> dec_fibers t)
let with_daemon t fn =
inc_fibers t;
t.daemon_fibers <- t.daemon_fibers + 1;
Fun.protect fn
~finally:(fun () ->
t.daemon_fibers <- t.daemon_fibers - 1;
dec_fibers t
)
let or_raise = function
| Ok x -> x
| Error ex -> raise ex
let rec await_idle t =
(* Wait for fibers to finish: *)
while t.fibers > 0 do
Trace.try_get t.cancel.id;
Single_waiter.await_protect t.waiter "Switch.await_idle" t.cancel.id
done;
(* Collect on_release handlers: *)
let queue = ref [] in
let enqueue n =
let fn = Lwt_dllist.get n in
Lwt_dllist.set n cancelled;
queue := fn :: !queue
in
Mutex.lock t.on_release_lock;
Option.iter (Lwt_dllist.iter_node_l enqueue) t.on_release;
t.on_release <- None;
Mutex.unlock t.on_release_lock;
(* Run on_release handlers *)
!queue |> List.iter (fun fn -> try Cancel.protect fn with ex -> fail t ex);
if t.fibers > 0 then await_idle t
let maybe_raise_exs t =
match t.exs with
| None -> ()
| Some (ex, bt) -> Printexc.raise_with_backtrace ex bt
let create cancel =
{
fibers = 1; (* The main function counts as a fiber *)
daemon_fibers = 0;
exs = None;
waiter = Single_waiter.create ();
on_release_lock = Mutex.create ();
on_release = Some (Lwt_dllist.create ());
cancel;
}
let run_internal t fn =
match fn t with
| v ->
dec_fibers t;
await_idle t;
Trace.get t.cancel.id;
maybe_raise_exs t; (* Check for failure while finishing *)
(* Success. *)
v
| exception ex ->
let bt = Printexc.get_raw_backtrace () in
(* Main function failed.
Turn the switch off to cancel any running fibers, if it's not off already. *)
dec_fibers t;
fail ~bt t ex;
await_idle t;
Trace.get t.cancel.id;
maybe_raise_exs t;
assert false
let run ?name fn = Cancel.sub_checked ?name Switch (fun cc -> run_internal (create cc) fn)
let run_protected ?name fn =
let ctx = Effect.perform Cancel.Get_context in
Cancel.with_cc ~ctx ~parent:ctx.cancel_context ~protected:true Switch @@ fun cancel ->
Option.iter (Trace.name cancel.id) name;
run_internal (create cancel) fn
(* Run [fn ()] in [t]'s cancellation context.
This prevents [t] from finishing until [fn] is done,
and means that cancelling [t] will cancel [fn]. *)
let run_in t fn =
with_op t @@ fun () ->
let ctx = Effect.perform Cancel.Get_context in
let old_cc = ctx.cancel_context in
Cancel.move_fiber_to t.cancel ctx;
match fn () with
| () -> Cancel.move_fiber_to old_cc ctx;
| exception ex -> Cancel.move_fiber_to old_cc ctx; raise ex
exception Release_error of string * exn
let () =
Printexc.register_printer (function
| Release_error (msg, ex) -> Some (Fmt.str "@[<v2>%s@,while handling %a@]" msg Exn.pp ex)
| _ -> None
)
let on_release_full t fn =
Mutex.lock t.on_release_lock;
match t.on_release with
| Some handlers ->
let node = Lwt_dllist.add_r fn handlers in
Mutex.unlock t.on_release_lock;
node
| None ->
Mutex.unlock t.on_release_lock;
match Cancel.protect fn with
| () -> invalid_arg "Switch finished!"
| exception ex ->
let bt = Printexc.get_raw_backtrace () in
Printexc.raise_with_backtrace (Release_error ("Switch finished!", ex)) bt
let on_release t fn =
ignore (on_release_full t fn : _ Lwt_dllist.node)
let on_release_cancellable t fn =
Hook (t.on_release_lock, on_release_full t fn)

Some files were not shown because too many files have changed in this diff Show More