Compare commits

...

89 Commits
v1.0 ... 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
79 changed files with 1442 additions and 954 deletions

View File

@ -12,7 +12,7 @@ jobs:
os: os:
- macos-latest - macos-latest
ocaml-compiler: ocaml-compiler:
- 5.1.x - 5.2.x
local-packages: local-packages:
- eio eio_posix eio_main - eio eio_posix eio_main
@ -44,7 +44,7 @@ jobs:
with: with:
opam-pin: false opam-pin: false
opam-depext: false opam-depext: false
ocaml-compiler: ocaml.5.1.0,ocaml-option-mingw ocaml-compiler: ocaml.5.2.0,ocaml-option-mingw
opam-repositories: | opam-repositories: |
dra27: https://github.com/dra27/opam-repository.git#windows-5.0 dra27: https://github.com/dra27/opam-repository.git#windows-5.0
normal: https://github.com/ocaml/opam-repository.git normal: https://github.com/ocaml/opam-repository.git
@ -57,6 +57,7 @@ jobs:
- run: opam exec -- dune build - run: opam exec -- dune build
- run: opam exec -- dune runtest - run: opam exec -- dune runtest
- run: opam exec -- dune exec -- ./examples/net/main.exe - run: opam exec -- dune exec -- ./examples/net/main.exe
- run: opam exec -- dune exec -- ./examples/fs/main.exe
docker: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

@ -1,3 +1,108 @@
## 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 ## v1.0
New features: New features:

View File

@ -1,8 +1,8 @@
FROM ocaml/opam:debian-11-ocaml-5.1 FROM ocaml/opam:debian-11-ocaml-5.2
# Make sure we're using opam-2.1: # Make sure we're using opam-2.1:
RUN sudo ln -sf /usr/bin/opam-2.1 /usr/bin/opam RUN sudo ln -sf /usr/bin/opam-2.1 /usr/bin/opam
# Ensure opam-repository is up-to-date: # Ensure opam-repository is up-to-date:
RUN cd opam-repository && git pull -q origin 0ac3fc79fd11ee365dd46119d43e9763cf57da52 && opam update RUN cd opam-repository && git pull -q origin 97de3378749cf8d2d70a5d710d310e5cc17c9dea && opam update
# Install utop for interactive use: # Install utop for interactive use:
RUN opam install utop fmt RUN opam install utop fmt
# Install Eio's dependencies (adding just the opam files first to help with caching): # Install Eio's dependencies (adding just the opam files first to help with caching):

View File

@ -135,4 +135,15 @@ Try to avoid making unnecessary changes; this makes review harder and clutters u
`ocamlformat` may be useful to get badly messed up code to a baseline unformatted state, `ocamlformat` may be useful to get badly messed up code to a baseline unformatted state,
from which human formatting can be added where needed. 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 [dscheck]: https://github.com/ocaml-multicore/dscheck

107
README.md
View File

@ -1,6 +1,6 @@
[API reference][Eio API] | [#eio Matrix chat](https://matrix.to/#/#eio:roscidus.com) | [Dev meetings][] [API reference][Eio API] | [#eio Matrix chat](https://matrix.to/#/#eio:roscidus.com) | [Dev meetings][]
# Eio -- Effects-Based Parallel IO for OCaml # Eio &mdash; Effects-Based Parallel IO for OCaml
Eio provides an effects-based direct-style IO stack for OCaml 5. Eio provides an effects-based direct-style IO stack for OCaml 5.
For example, you can use Eio to read and write files, make network connections, For example, you can use Eio to read and write files, make network connections,
@ -15,9 +15,8 @@ Eio replaces existing concurrency libraries such as Lwt
<!-- vim-markdown-toc GFM --> <!-- vim-markdown-toc GFM -->
* [Motivation](#motivation) * [Motivation](#motivation)
* [Current Status](#current-status) * [Eio packages](#eio-packages)
* [Structure of the Code](#structure-of-the-code) * [Getting OCaml](#getting-ocaml)
* [Getting OCaml 5.1](#getting-ocaml-51)
* [Getting Eio](#getting-eio) * [Getting Eio](#getting-eio)
* [Running Eio](#running-eio) * [Running Eio](#running-eio)
* [Testing with Mocks](#testing-with-mocks) * [Testing with Mocks](#testing-with-mocks)
@ -80,24 +79,19 @@ Additionally, modern operating systems provide high-performance alternatives to
For example, Linux's io_uring system has applications write the operations they want to perform to a ring buffer, For example, Linux's io_uring system has applications write the operations they want to perform to a ring buffer,
which Linux handles asynchronously, and Eio can take advantage of this. which Linux handles asynchronously, and Eio can take advantage of this.
## Current Status You can always [fall back to using Lwt libraries](#lwt) to provide missing features if necessary.
See [Awesome Multicore OCaml][] for links to other projects using Eio.
See [Eio 1.0 progress tracking](https://github.com/ocaml-multicore/eio/issues/388) for the current status. ## Eio packages
Please try porting your programs to use Eio and submit PRs or open issues when you find problems.
Remember that you can always [fall back to using Lwt libraries](#lwt) to provide missing features if necessary.
See [Awesome Multicore OCaml][] for links to work migrating other projects to Eio.
## Structure of the Code
- [Eio][] provides concurrency primitives (promises, etc.) and a high-level, cross-platform OS API. - [Eio][] provides concurrency primitives (promises, etc.) and a high-level, cross-platform OS API.
- [Eio_posix][] provides a cross-platform backend for these APIs for POSIX-type systems. - [Eio_posix][] provides a cross-platform backend for these APIs for POSIX-type systems.
- [Eio_linux][] provides a Linux io-uring backend for these APIs, - [Eio_linux][] provides a Linux io_uring backend for these APIs.
plus a low-level API that can be used directly (in non-portable code).
- [Eio_windows][] is for use on Windows (incomplete - [help wanted](https://github.com/ocaml-multicore/eio/issues/125)). - [Eio_windows][] is for use on Windows (incomplete - [help wanted](https://github.com/ocaml-multicore/eio/issues/125)).
- [Eio_main][] selects an appropriate backend (e.g. `eio_linux` or `eio_posix`), depending on your platform. - [Eio_main][] selects an appropriate backend (e.g. `eio_linux` or `eio_posix`), depending on your platform.
- [Eio_js][] allows Eio code to run in the browser, using `js_of_ocaml`.
## Getting OCaml 5.1 ## Getting OCaml
You'll need OCaml 5.1.0 or later. You'll need OCaml 5.1.0 or later.
You can either install it yourself or build the included [Dockerfile](./Dockerfile). You can either install it yourself or build the included [Dockerfile](./Dockerfile).
@ -109,7 +103,7 @@ To install it yourself:
2. Use opam to install OCaml: 2. Use opam to install OCaml:
``` ```
opam switch create 5.1.1 opam switch create 5.2.0
``` ```
## Getting Eio ## Getting Eio
@ -135,18 +129,18 @@ prompt and return after each line.)
# open Eio.Std;; # open Eio.Std;;
``` ```
This function writes a greeting to `stdout` using [Eio.Flow][]: This function writes a greeting to `out` using [Eio.Flow][]:
```ocaml ```ocaml
let main ~stdout = let main out =
Eio.Flow.copy_string "Hello, world!\n" stdout Eio.Flow.copy_string "Hello, world!\n" out
``` ```
We use [Eio_main.run][] to run the event loop and call `main` from there: We use [Eio_main.run][] to run the event loop and call `main` from there:
```ocaml ```ocaml
# Eio_main.run @@ fun env -> # Eio_main.run @@ fun env ->
main ~stdout:(Eio.Stdenv.stdout env);; main (Eio.Stdenv.stdout env);;
Hello, world! Hello, world!
- : unit = () - : unit = ()
``` ```
@ -156,7 +150,7 @@ Note that:
- The `env` argument represents the standard environment of a Unix process, allowing it to interact with the outside world. - The `env` argument represents the standard environment of a Unix process, allowing it to interact with the outside world.
A program will typically start by extracting from `env` whatever things the program will need and then calling `main` with them. A program will typically start by extracting from `env` whatever things the program will need and then calling `main` with them.
- The type of the `main` function here tells us that this program only interacts via `stdout`. - The type of the `main` function here tells us that this program only interacts via the `out` flow.
- `Eio_main.run` automatically calls the appropriate run function for your platform. - `Eio_main.run` automatically calls the appropriate run function for your platform.
For example, on Linux this will call `Eio_linux.run`. For non-portable code you can use the platform-specific library directly. For example, on Linux this will call `Eio_linux.run`. For non-portable code you can use the platform-specific library directly.
@ -171,7 +165,7 @@ For example, instead of giving `main` the real standard output, we can have it w
```ocaml ```ocaml
# Eio_main.run @@ fun _env -> # Eio_main.run @@ fun _env ->
let buffer = Buffer.create 20 in let buffer = Buffer.create 20 in
main ~stdout:(Eio.Flow.buffer_sink buffer); main (Eio.Flow.buffer_sink buffer);
traceln "Main would print %S" (Buffer.contents buffer);; traceln "Main would print %S" (Buffer.contents buffer);;
+Main would print "Hello, world!\n" +Main would print "Hello, world!\n"
- : unit = () - : unit = ()
@ -185,8 +179,7 @@ The [Eio_mock][] library provides some convenient pre-built mocks:
```ocaml ```ocaml
# #require "eio.mock";; # #require "eio.mock";;
# Eio_main.run @@ fun _env -> # Eio_main.run @@ fun _env ->
let mock_stdout = Eio_mock.Flow.make "mock-stdout" in main (Eio_mock.Flow.make "mock-stdout");;
main ~stdout:mock_stdout;;
+mock-stdout: wrote "Hello, world!\n" +mock-stdout: wrote "Hello, world!\n"
- : unit = () - : unit = ()
``` ```
@ -238,12 +231,7 @@ The green segments show when each fiber is running.
Note that the output from `traceln` appears in the trace as well as on the console. Note that the output from `traceln` appears in the trace as well as on the console.
In the eio-trace window, scrolling with the mouse or touchpad will zoom in or out of the diagram. In the eio-trace window, scrolling with the mouse or touchpad will zoom in or out of the diagram.
There are various third-party tools that can also consume this data Third-party tools, such as [Olly][], can also consume this data.
(but may currently require patches to support the new system):
- [Meio][] (Monitoring for Eio) provides an interactive console-based UI for exploring running fibers.
- [Olly][] can save Perfetto traces and report statistics.
[examples/trace](./examples/trace/) shows how to consume the events manually. [examples/trace](./examples/trace/) shows how to consume the events manually.
## Cancellation ## Cancellation
@ -317,22 +305,23 @@ For example:
```ocaml ```ocaml
# Eio_main.run @@ fun _env -> # Eio_main.run @@ fun _env ->
Switch.run (fun sw -> Switch.run (fun sw ->
Fiber.fork ~sw for i = 1 to 3 do
(fun () -> for i = 1 to 3 do traceln "i = %d" i; Fiber.yield () done); Fiber.fork ~sw (fun () ->
traceln "First thread forked"; traceln "Job %d starting" i;
Fiber.fork ~sw Fiber.yield ();
(fun () -> for j = 1 to 3 do traceln "j = %d" j; Fiber.yield () done); traceln "%d done" i;
traceln "Second thread forked; top-level code is finished" );
done;
traceln "All child fibers forked";
); );
traceln "Switch is finished";; traceln "Switch is finished";;
+i = 1 +Job 1 starting
+First thread forked +Job 2 starting
+j = 1 +Job 3 starting
+Second thread forked; top-level code is finished +All child fibers forked
+i = 2 +1 done
+j = 2 +2 done
+i = 3 +3 done
+j = 3
+Switch is finished +Switch is finished
- : unit = () - : unit = ()
``` ```
@ -397,6 +386,7 @@ let run_client ~net ~addr =
Switch.run ~name:"client" @@ fun sw -> Switch.run ~name:"client" @@ fun sw ->
traceln "Client: connecting to server"; traceln "Client: connecting to server";
let flow = Eio.Net.connect ~sw net addr in let flow = Eio.Net.connect ~sw net addr in
(* Read all data until end-of-stream (shutdown): *)
traceln "Client: received %S" (Eio.Flow.read_all flow) traceln "Client: received %S" (Eio.Flow.read_all flow)
``` ```
@ -934,6 +924,9 @@ The mock backend provides a mock clock that advances automatically where there i
- : unit = () - : unit = ()
``` ```
Note: You could also just use `Eio_unix.sleep 5.0` if you don't want to pass a clock around.
This is especially useful if you need to insert a delay for some quick debugging.
## Multicore Support ## Multicore Support
OCaml allows a program to create multiple *domains* in which to run code, allowing multiple CPUs to be used at once. OCaml allows a program to create multiple *domains* in which to run code, allowing multiple CPUs to be used at once.
@ -1017,12 +1010,8 @@ Usually you will only want one pool for an entire application, so the pool is ty
let () = let () =
Eio_main.run @@ fun env -> Eio_main.run @@ fun env ->
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
let pool = let dm = Eio.Stdenv.domain_mgr env in
Eio.Executor_pool.create main ~pool:(Eio.Executor_pool.create ~sw ~domain_count:2 dm)
~sw (Eio.Stdenv.domain_mgr env)
~domain_count:4
in
main ~pool
``` ```
The pool starts its domain workers immediately upon creation. The pool starts its domain workers immediately upon creation.
@ -1036,11 +1025,7 @@ The total number of domains should not exceed `Domain.recommended_domain_count`
We can run the previous example using an Executor Pool like this: We can run the previous example using an Executor Pool like this:
```ocaml ```ocaml
let main ~domain_mgr = let main ~pool =
Switch.run @@ fun sw ->
let pool =
Eio.Executor_pool.create ~sw domain_mgr ~domain_count:4
in
let test n = let test n =
traceln "sum 1..%d = %d" n traceln "sum 1..%d = %d" n
(Eio.Executor_pool.submit_exn pool ~weight:1.0 (Eio.Executor_pool.submit_exn pool ~weight:1.0
@ -1054,7 +1039,9 @@ let main ~domain_mgr =
<!-- $MDX non-deterministic=output --> <!-- $MDX non-deterministic=output -->
```ocaml ```ocaml
# Eio_main.run @@ fun env -> # Eio_main.run @@ fun env ->
main ~domain_mgr:(Eio.Stdenv.domain_mgr env);; Switch.run @@ fun sw ->
let dm = Eio.Stdenv.domain_mgr env in
main ~pool:(Eio.Executor_pool.create ~sw ~domain_count:2 dm)
+Starting CPU-intensive task... +Starting CPU-intensive task...
+Starting CPU-intensive task... +Starting CPU-intensive task...
+Finished +Finished
@ -1593,6 +1580,9 @@ In particular, if you test your code by providing (deterministic) mocks then the
An easy way to write tests is by having the mocks call `traceln` and then comparing the trace output with the expected output. An easy way to write tests is by having the mocks call `traceln` and then comparing the trace output with the expected output.
See Eio's own tests for examples, e.g., [tests/switch.md](tests/switch.md). See Eio's own tests for examples, e.g., [tests/switch.md](tests/switch.md).
Note: this only applies to the high-level APIs in the `Eio` module.
Programs can behave non-deterministically when using `Eio_unix` or the various `Low_level` APIs provided by the backends.
## Provider Interfaces ## Provider Interfaces
Eio applications use resources by calling functions (such as `Eio.Flow.write`). Eio applications use resources by calling functions (such as `Eio.Flow.write`).
@ -1631,7 +1621,8 @@ It can then be used like any other Eio flow:
## Example Applications ## Example Applications
- [gemini-eio][] is a simple Gemini browser. It shows how to integrate Eio with `ocaml-tls` and `notty`. - [gemini-eio][] is a simple Gemini browser. It shows how to integrate Eio with `ocaml-tls` and `notty`.
- [ocaml-multicore/retro-httpaf-bench](https://github.com/ocaml-multicore/retro-httpaf-bench) includes a simple HTTP server using Eio. It shows how to use Eio with `httpaf`, and how to use multiple domains for increased performance. - [cohttp-eio/examples](https://github.com/mirage/ocaml-cohttp/tree/master/cohttp-eio/examples) shows how to use Eio with HTTP.
- [capnp-rpc](https://github.com/mirage/capnp-rpc) shows how to use Eio with Cap'n Proto.
- [Awesome Multicore OCaml][] lists many other projects. - [Awesome Multicore OCaml][] lists many other projects.
## Integrations ## Integrations
@ -1905,10 +1896,10 @@ Some background about the effects system can be found in:
[Eio.Condition]: https://ocaml-multicore.github.io/eio/eio/Eio/Condition/index.html [Eio.Condition]: https://ocaml-multicore.github.io/eio/eio/Eio/Condition/index.html
[Domainslib]: https://github.com/ocaml-multicore/domainslib [Domainslib]: https://github.com/ocaml-multicore/domainslib
[kcas]: https://github.com/ocaml-multicore/kcas [kcas]: https://github.com/ocaml-multicore/kcas
[Meio]: https://github.com/tarides/meio
[Lambda Capabilities]: https://roscidus.com/blog/blog/2023/04/26/lambda-capabilities/ [Lambda Capabilities]: https://roscidus.com/blog/blog/2023/04/26/lambda-capabilities/
[Eio.Process]: https://ocaml-multicore.github.io/eio/eio/Eio/Process/index.html [Eio.Process]: https://ocaml-multicore.github.io/eio/eio/Eio/Process/index.html
[Dev meetings]: https://docs.google.com/document/d/1ZBfbjAkvEkv9ldumpZV5VXrEc_HpPeYjHPW_TiwJe4Q [Dev meetings]: https://docs.google.com/document/d/1ZBfbjAkvEkv9ldumpZV5VXrEc_HpPeYjHPW_TiwJe4Q
[Olly]: https://github.com/tarides/runtime_events_tools [Olly]: https://github.com/tarides/runtime_events_tools
[eio-trace]: https://github.com/ocaml-multicore/eio-trace [eio-trace]: https://github.com/ocaml-multicore/eio-trace
[cap_enter]: https://man.freebsd.org/cgi/man.cgi?query=cap_enter [cap_enter]: https://man.freebsd.org/cgi/man.cgi?query=cap_enter
[eio_js]: https://github.com/ocaml-multicore/eio_js

View File

@ -1,8 +1,8 @@
FROM ocaml/opam:debian-11-ocaml-5.1 FROM ocaml/opam:debian-11-ocaml-5.2
# Make sure we're using opam-2.1: # Make sure we're using opam-2.1:
RUN sudo ln -sf /usr/bin/opam-2.1 /usr/bin/opam RUN sudo ln -sf /usr/bin/opam-2.1 /usr/bin/opam
# Ensure opam-repository is up-to-date: # Ensure opam-repository is up-to-date:
RUN cd opam-repository && git pull -q origin 0ac3fc79fd11ee365dd46119d43e9763cf57da52 && opam update 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): # Install Eio's dependencies (adding just the opam files first to help with caching):
RUN mkdir eio RUN mkdir eio
WORKDIR eio WORKDIR eio

View File

@ -45,6 +45,19 @@ let () =
"metrics", `List metrics; "metrics", `List metrics;
] ]
in 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 [ 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); "results", `List (List.map run benchmarks);
] ]

Binary file not shown.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"> <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> <defs>
<g> <g>
<symbol overflow="visible" id="glyph0-0"> <symbol overflow="visible" id="glyph0-0">
@ -81,489 +81,450 @@
<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 "/> <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>
<symbol overflow="visible" id="glyph1-1"> <symbol overflow="visible" id="glyph1-1">
<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 "/> <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>
<symbol overflow="visible" id="glyph1-2"> <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 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-8">
<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-9">
<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-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>
<symbol overflow="visible" id="glyph1-12">
<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-13">
<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-14">
<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-15">
<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-16">
<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 "/> <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>
<symbol overflow="visible" id="glyph1-17"> <symbol overflow="visible" id="glyph1-3">
<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 "/> <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>
<symbol overflow="visible" id="glyph1-18"> <symbol overflow="visible" id="glyph1-4">
<path style="stroke:none;" d="M 0.75 -4.375 L 1.46875 -4.375 L 1.46875 0.078125 C 1.46875 0.640625 1.363281 1.046875 1.15625 1.296875 C 0.945312 1.546875 0.601562 1.671875 0.125 1.671875 L -0.140625 1.671875 L -0.140625 1.0625 L 0.046875 1.0625 C 0.316406 1.0625 0.5 0.992188 0.59375 0.859375 C 0.695312 0.734375 0.75 0.472656 0.75 0.078125 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 "/> <path style="stroke:none;" d=""/>
</symbol> </symbol>
<symbol overflow="visible" id="glyph1-19"> <symbol overflow="visible" id="glyph1-5">
<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 "/> <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>
<symbol overflow="visible" id="glyph1-20"> <symbol overflow="visible" id="glyph1-6">
<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 "/> <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>
<symbol overflow="visible" id="glyph1-21"> <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 "/> <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>
<symbol overflow="visible" id="glyph1-22"> <symbol overflow="visible" id="glyph1-12">
<path style="stroke:none;" d="M 0.9375 -4.140625 L 1.765625 -4.140625 L 1.765625 -3.140625 L 0.9375 -3.140625 Z M 0.9375 -1 L 1.765625 -1 L 1.765625 -0.328125 L 1.125 0.9375 L 0.625 0.9375 L 0.9375 -0.328125 Z M 0.9375 -1 "/> <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>
<symbol overflow="visible" id="glyph1-23"> <symbol overflow="visible" id="glyph1-13">
<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 "/> <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>
<symbol overflow="visible" id="glyph1-24"> <symbol overflow="visible" id="glyph1-14">
<path style="stroke:none;" d="M 0.390625 -2.515625 L 2.5 -2.515625 L 2.5 -1.875 L 0.390625 -1.875 Z M 0.390625 -2.515625 "/> <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>
<symbol overflow="visible" id="glyph1-25"> <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 "/> <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>
<symbol overflow="visible" id="glyph1-26"> <symbol overflow="visible" id="glyph1-19">
<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 "/> <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>
<symbol overflow="visible" id="glyph1-27"> <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 "/> <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>
<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> </g>
<clipPath id="clip1"> <clipPath id="clip1">
<path d="M 683.339844 48 L 725 48 L 725 59 L 683.339844 59 Z M 683.339844 48 "/> <path d="M 1149.457031 48 L 1191 48 L 1191 59 L 1149.457031 59 Z M 1149.457031 48 "/>
</clipPath> </clipPath>
<clipPath id="clip2"> <clipPath id="clip2">
<path d="M 812.351562 48 L 847.65625 48 L 847.65625 59 L 812.351562 59 Z M 812.351562 48 "/> <path d="M 168.484375 80 L 210 80 L 210 91 L 168.484375 91 Z M 168.484375 80 "/>
</clipPath> </clipPath>
<clipPath id="clip3"> <clipPath id="clip3">
<path d="M 1068.0625 48 L 1099.449219 48 L 1099.449219 59 L 1068.0625 59 Z M 1068.0625 48 "/> <path d="M 945.597656 80 L 987 80 L 987 91 L 945.597656 91 Z M 945.597656 80 "/>
</clipPath> </clipPath>
<clipPath id="clip4"> <clipPath id="clip4">
<path d="M 865.34375 80 L 898 80 L 898 91 L 865.34375 91 Z M 865.34375 80 "/> <path d="M 250.011719 69 L 303 69 L 303 78 L 250.011719 78 Z M 250.011719 69 "/>
</clipPath> </clipPath>
<clipPath id="clip5"> <clipPath id="clip5">
<path d="M 951.027344 80 L 981.15625 80 L 981.15625 91 L 951.027344 91 Z M 951.027344 80 "/> <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>
<clipPath id="clip6"> <clipPath id="clip6">
<path d="M 764.328125 112 L 801.253906 112 L 801.253906 123 L 764.328125 123 Z M 764.328125 112 "/> <path d="M 1018.117188 112 L 1060 112 L 1060 123 L 1018.117188 123 Z M 1018.117188 112 "/>
</clipPath> </clipPath>
<clipPath id="clip7"> <clipPath id="clip7">
<path d="M 910 112 L 939.957031 112 L 939.957031 123 L 910 123 Z M 910 112 "/> <path d="M 583.375 101 L 637 101 L 637 110 L 583.375 110 Z M 583.375 101 "/>
</clipPath> </clipPath>
<clipPath id="clip8"> <clipPath id="clip8">
<path d="M 1007.089844 112 L 1022.230469 112 L 1022.230469 123 L 1007.089844 123 Z M 1007.089844 112 "/> <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>
<clipPath id="clip9"> <clipPath id="clip9">
<path d="M 789.792969 101 L 809 101 L 809 110 L 789.792969 110 Z M 789.792969 101 "/> <path d="M 1082.304688 144 L 1124 144 L 1124 155 L 1082.304688 155 Z M 1082.304688 144 "/>
</clipPath> </clipPath>
<clipPath id="clip10"> <clipPath id="clip10">
<path d="M 931.160156 101 L 950 101 L 950 110 L 931.160156 110 Z M 931.160156 101 "/> <path d="M 819.433594 133 L 873 133 L 873 142 L 819.433594 142 Z M 819.433594 133 "/>
</clipPath> </clipPath>
<clipPath id="clip11"> <clipPath id="clip11">
<path d="M 1013.980469 101 L 1033 101 L 1033 110 L 1013.980469 110 Z M 1013.980469 101 "/> <path d="M 1112 133 L 1132.316406 133 L 1132.316406 141 L 1112 141 Z M 1112 133 "/>
</clipPath> </clipPath>
<clipPath id="clip12"> <clipPath id="clip12">
<path d="M 836.167969 37 L 1028 37 L 1028 46 L 836.167969 46 Z M 836.167969 37 "/> <path d="M 901.039062 37 L 985 37 L 985 45 L 901.039062 45 Z M 901.039062 37 "/>
</clipPath> </clipPath>
<clipPath id="clip13"> <clipPath id="clip13">
<path d="M 1088.320312 37 L 1158 37 L 1158 45 L 1088.320312 45 Z M 1088.320312 37 "/> <path d="M 1179.296875 37 L 1200.945312 37 L 1200.945312 45 L 1179.296875 45 Z M 1179.296875 37 "/>
</clipPath> </clipPath>
</defs> </defs>
<g id="surface2"> <g id="surface2">
<rect x="0" y="0" width="1280" height="152" style="fill:rgb(90%,90%,90%);fill-opacity:1;stroke:none;"/> <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 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 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 155.40625 0 L 155.40625 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 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 306.808594 0 L 306.808594 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 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 458.214844 0 L 458.214844 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 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 609.621094 0 L 609.621094 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 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 761.027344 0 L 761.027344 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 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 912.429688 0 L 912.429688 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 1153.917969 0 L 1153.917969 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 1063.835938 0 L 1063.835938 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 1215.242188 0 L 1215.242188 152 "/>
<g style="fill:rgb(40%,40%,40%);fill-opacity:1;"> <g style="fill:rgb(40%,40%,40%);fill-opacity:1;">
<use xlink:href="#glyph0-1" x="4" y="147.503906"/> <use xlink:href="#glyph0-1" x="4" y="179.503906"/>
<use xlink:href="#glyph0-2" x="11.582031" y="147.503906"/> <use xlink:href="#glyph0-2" x="11.582031" y="179.503906"/>
<use xlink:href="#glyph0-3" x="18.935547" y="147.503906"/> <use xlink:href="#glyph0-3" x="18.935547" y="179.503906"/>
<use xlink:href="#glyph0-4" x="25.533203" y="147.503906"/> <use xlink:href="#glyph0-4" x="25.533203" y="179.503906"/>
<use xlink:href="#glyph0-5" x="33.138672" y="147.503906"/> <use xlink:href="#glyph0-5" x="33.138672" y="179.503906"/>
<use xlink:href="#glyph0-6" x="36.953125" y="147.503906"/> <use xlink:href="#glyph0-6" x="36.953125" y="179.503906"/>
<use xlink:href="#glyph0-7" x="44.570312" y="147.503906"/> <use xlink:href="#glyph0-7" x="44.570312" y="179.503906"/>
<use xlink:href="#glyph0-8" x="49.503906" y="147.503906"/> <use xlink:href="#glyph0-8" x="49.503906" y="179.503906"/>
<use xlink:href="#glyph0-9" x="52.837891" y="147.503906"/> <use xlink:href="#glyph0-9" x="52.837891" y="179.503906"/>
<use xlink:href="#glyph0-5" x="60.455078" y="147.503906"/> <use xlink:href="#glyph0-5" x="60.455078" y="179.503906"/>
<use xlink:href="#glyph0-9" x="64.269531" y="147.503906"/> <use xlink:href="#glyph0-9" x="64.269531" y="179.503906"/>
<use xlink:href="#glyph0-8" x="71.886719" y="147.503906"/> <use xlink:href="#glyph0-8" x="71.886719" y="179.503906"/>
<use xlink:href="#glyph0-10" x="75.220703" y="147.503906"/> <use xlink:href="#glyph0-10" x="75.220703" y="179.503906"/>
<use xlink:href="#glyph0-8" x="82.322266" y="147.503906"/> <use xlink:href="#glyph0-8" x="82.322266" y="179.503906"/>
<use xlink:href="#glyph0-11" x="85.65625" y="147.503906"/> <use xlink:href="#glyph0-11" x="85.65625" y="179.503906"/>
<use xlink:href="#glyph0-8" x="91.908203" y="147.503906"/> <use xlink:href="#glyph0-8" x="91.908203" y="179.503906"/>
<use xlink:href="#glyph0-12" x="95.242188" y="147.503906"/> <use xlink:href="#glyph0-12" x="95.242188" y="179.503906"/>
<use xlink:href="#glyph0-13" x="102.583984" y="147.503906"/> <use xlink:href="#glyph0-13" x="102.583984" y="179.503906"/>
<use xlink:href="#glyph0-14" x="110.189453" y="147.503906"/> <use xlink:href="#glyph0-14" x="110.189453" y="179.503906"/>
<use xlink:href="#glyph0-5" x="114.232422" y="147.503906"/> <use xlink:href="#glyph0-5" x="114.232422" y="179.503906"/>
<use xlink:href="#glyph0-15" x="118.046875" y="147.503906"/> <use xlink:href="#glyph0-15" x="118.046875" y="179.503906"/>
<use xlink:href="#glyph0-16" x="125.681641" y="147.503906"/> <use xlink:href="#glyph0-16" x="125.681641" y="179.503906"/>
<use xlink:href="#glyph0-5" x="133.316406" y="147.503906"/> <use xlink:href="#glyph0-5" x="133.316406" y="179.503906"/>
<use xlink:href="#glyph0-17" x="137.130859" y="147.503906"/> <use xlink:href="#glyph0-17" x="137.130859" y="179.503906"/>
<use xlink:href="#glyph0-11" x="144.736328" y="147.503906"/> <use xlink:href="#glyph0-11" x="144.736328" y="179.503906"/>
</g> </g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 11.480469 46 L 18.324219 46 L 18.324219 60 L 11.480469 60 Z M 11.480469 46 "/> <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.324219 46 L 93.210938 46 L 93.210938 60 L 18.324219 60 Z M 18.324219 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 93.207031 46 L 671.605469 46 L 671.605469 60 L 93.207031 60 Z M 93.207031 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 671.605469 46 L 681.339844 46 L 681.339844 60 L 671.605469 60 Z M 671.605469 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(50%,90%,50%);fill-opacity:1;" d="M 681.339844 46 L 732.652344 46 L 732.652344 60 L 681.339844 60 Z M 681.339844 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 clip-path="url(#clip1)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="683.339844" y="58"/> <use xlink:href="#glyph0-18" x="1149.457031" y="58"/>
<use xlink:href="#glyph0-7" x="688.044922" y="58"/> <use xlink:href="#glyph0-7" x="1154.162109" y="58"/>
<use xlink:href="#glyph0-2" x="692.978516" y="58"/> <use xlink:href="#glyph0-2" x="1159.095703" y="58"/>
<use xlink:href="#glyph0-3" x="700.332031" y="58"/> <use xlink:href="#glyph0-3" x="1166.449219" y="58"/>
<use xlink:href="#glyph0-19" x="706.929688" y="58"/> <use xlink:href="#glyph0-19" x="1173.046875" y="58"/>
<use xlink:href="#glyph0-20" x="714.3125" y="58"/> <use xlink:href="#glyph0-20" x="1180.429688" y="58"/>
<use xlink:href="#glyph0-13" x="717.646484" y="58"/> <use xlink:href="#glyph0-13" x="1183.763672" y="58"/>
</g> </g>
</g> </g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 732.652344 46 L 740.554688 46 L 740.554688 60 L 732.652344 60 Z M 732.652344 46 "/> <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%,40%,40%);fill-opacity:1;" d="M 740.554688 46 L 805.128906 46 L 805.128906 60 L 740.554688 60 Z M 740.554688 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 805.128906 46 L 810.351562 46 L 810.351562 60 L 805.128906 60 Z M 805.128906 46 "/> <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 810.351562 46 L 847.65625 46 L 847.65625 60 L 810.351562 60 Z M 810.351562 46 "/> <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 clip-path="url(#clip2)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="812.351562" y="58"/> <use xlink:href="#glyph0-18" x="168.484375" y="90"/>
<use xlink:href="#glyph0-7" x="817.056641" y="58"/> <use xlink:href="#glyph0-7" x="173.189453" y="90"/>
<use xlink:href="#glyph0-2" x="821.990234" y="58"/> <use xlink:href="#glyph0-2" x="178.123047" y="90"/>
<use xlink:href="#glyph0-3" x="829.34375" y="58"/> <use xlink:href="#glyph0-3" x="185.476562" y="90"/>
<use xlink:href="#glyph0-19" x="835.941406" y="58"/> <use xlink:href="#glyph0-19" x="192.074219" y="90"/>
<use xlink:href="#glyph0-20" x="843.324219" y="58"/> <use xlink:href="#glyph0-20" x="199.457031" y="90"/>
<use xlink:href="#glyph0-13" x="846.658203" y="58"/> <use xlink:href="#glyph0-13" x="202.791016" y="90"/>
</g> </g>
</g> </g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 847.660156 46 L 852.671875 46 L 852.671875 60 L 847.660156 60 Z M 847.660156 46 "/> <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 852.671875 46 L 1053.917969 46 L 1053.917969 60 L 852.671875 60 Z M 852.671875 46 "/> <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 "/>
<g style="fill:rgb(100%,100%,100%);fill-opacity:1;"> <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 "/>
<use xlink:href="#glyph0-21" x="854.671875" y="58"/> <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 "/>
<use xlink:href="#glyph0-22" x="862.289062" y="58"/>
<use xlink:href="#glyph0-8" x="872.103516" y="58"/>
<use xlink:href="#glyph0-18" x="875.4375" y="58"/>
<use xlink:href="#glyph0-3" x="880.142578" y="58"/>
<use xlink:href="#glyph0-4" x="886.740234" y="58"/>
<use xlink:href="#glyph0-23" x="894.345703" y="58"/>
<use xlink:href="#glyph0-2" x="898.160156" y="58"/>
<use xlink:href="#glyph0-22" x="905.513672" y="58"/>
<use xlink:href="#glyph0-2" x="915.328125" y="58"/>
<use xlink:href="#glyph0-8" x="922.681641" y="58"/>
<use xlink:href="#glyph0-18" x="926.015625" y="58"/>
<use xlink:href="#glyph0-24" x="930.720703" y="58"/>
<use xlink:href="#glyph0-8" x="936.720703" y="58"/>
<use xlink:href="#glyph0-9" x="940.054688" y="58"/>
<use xlink:href="#glyph0-20" x="947.671875" y="58"/>
<use xlink:href="#glyph0-19" x="951.005859" y="58"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1053.917969 46 L 1066.0625 46 L 1066.0625 60 L 1053.917969 60 Z M 1053.917969 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1066.0625 46 L 1099.449219 46 L 1099.449219 60 L 1066.0625 60 Z M 1066.0625 46 "/>
<g clip-path="url(#clip3)" clip-rule="nonzero"> <g clip-path="url(#clip3)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="1068.0625" y="58"/> <use xlink:href="#glyph0-18" x="945.597656" y="90"/>
<use xlink:href="#glyph0-7" x="1072.767578" y="58"/> <use xlink:href="#glyph0-7" x="950.302734" y="90"/>
<use xlink:href="#glyph0-2" x="1077.701172" y="58"/> <use xlink:href="#glyph0-2" x="955.236328" y="90"/>
<use xlink:href="#glyph0-3" x="1085.054688" y="58"/> <use xlink:href="#glyph0-3" x="962.589844" y="90"/>
<use xlink:href="#glyph0-19" x="1091.652344" y="58"/> <use xlink:href="#glyph0-19" x="969.1875" y="90"/>
<use xlink:href="#glyph0-20" x="1099.035156" y="58"/> <use xlink:href="#glyph0-20" x="976.570312" y="90"/>
<use xlink:href="#glyph0-13" x="1102.369141" y="58"/> <use xlink:href="#glyph0-13" x="979.904297" y="90"/>
</g> </g>
</g> </g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1099.445312 46 L 1190.574219 46 L 1190.574219 60 L 1099.445312 60 Z M 1099.445312 46 "/> <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%,80%,40%);fill-opacity:1;" d="M 93.207031 78 L 97.839844 78 L 97.839844 92 L 93.207031 92 Z M 93.207031 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=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 97.839844 78 L 311.714844 78 L 311.714844 92 L 97.839844 92 Z M 97.839844 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 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 311.714844 78 L 657.222656 78 L 657.222656 92 L 311.714844 92 Z M 311.714844 78 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="313.714844" y="90"/>
<use xlink:href="#glyph0-7" x="318.419922" y="90"/>
<use xlink:href="#glyph0-2" x="323.353516" y="90"/>
<use xlink:href="#glyph0-3" x="330.707031" y="90"/>
<use xlink:href="#glyph0-19" x="337.304688" y="90"/>
<use xlink:href="#glyph0-20" x="344.6875" y="90"/>
<use xlink:href="#glyph0-13" x="348.021484" y="90"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 657.222656 78 L 662.550781 78 L 662.550781 92 L 657.222656 92 Z M 657.222656 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 662.550781 78 L 856.289062 78 L 856.289062 92 L 662.550781 92 Z M 662.550781 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 856.289062 78 L 863.34375 78 L 863.34375 92 L 856.289062 92 Z M 856.289062 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 863.34375 78 L 898 78 L 898 92 L 863.34375 92 Z M 863.34375 78 "/>
<g clip-path="url(#clip4)" clip-rule="nonzero"> <g clip-path="url(#clip4)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="865.34375" y="90"/> <use xlink:href="#glyph1-1" x="250.011719" y="76"/>
<use xlink:href="#glyph0-7" x="870.048828" y="90"/> <use xlink:href="#glyph1-2" x="252.371094" y="76"/>
<use xlink:href="#glyph0-2" x="874.982422" y="90"/> <use xlink:href="#glyph1-3" x="257.265625" y="76"/>
<use xlink:href="#glyph0-3" x="882.335938" y="90"/> <use xlink:href="#glyph1-4" x="262.34375" y="76"/>
<use xlink:href="#glyph0-19" x="888.933594" y="90"/> <use xlink:href="#glyph1-5" x="264.886719" y="76"/>
<use xlink:href="#glyph0-20" x="896.316406" y="90"/> <use xlink:href="#glyph1-4" x="269.976562" y="76"/>
<use xlink:href="#glyph0-13" x="899.650391" y="90"/> <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>
</g> </g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 898 78 L 899.648438 78 L 899.648438 92 L 898 92 Z M 898 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 977.003906 81 L 977.003906 75 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 899.652344 78 L 943.257812 78 L 943.257812 92 L 899.652344 92 Z M 899.652344 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 943.257812 78 L 949.027344 78 L 949.027344 92 L 943.257812 92 Z M 943.257812 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 949.027344 78 L 981.15625 78 L 981.15625 92 L 949.027344 92 Z M 949.027344 78 "/>
<g clip-path="url(#clip5)" clip-rule="nonzero"> <g clip-path="url(#clip5)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="951.027344" y="90"/> <use xlink:href="#glyph1-5" x="979.003906" y="76"/>
<use xlink:href="#glyph0-7" x="955.732422" y="90"/> <use xlink:href="#glyph1-4" x="984.09375" y="76"/>
<use xlink:href="#glyph0-2" x="960.666016" y="90"/> <use xlink:href="#glyph1-13" x="986.636719" y="76"/>
<use xlink:href="#glyph0-3" x="968.019531" y="90"/> <use xlink:href="#glyph1-2" x="991.714844" y="76"/>
<use xlink:href="#glyph0-19" x="974.617188" y="90"/> <use xlink:href="#glyph1-11" x="996.609375" y="76"/>
<use xlink:href="#glyph0-20" x="982" y="90"/> <use xlink:href="#glyph1-14" x="1001.679688" y="76"/>
<use xlink:href="#glyph0-13" x="985.333984" y="90"/>
</g> </g>
</g> </g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 981.152344 78 L 982.46875 78 L 982.46875 92 L 981.152344 92 Z M 981.152344 78 "/> <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%,40%,40%);fill-opacity:1;" d="M 982.472656 78 L 1041.113281 78 L 1041.113281 92 L 982.472656 92 Z M 982.472656 78 "/> <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 1041.109375 78 L 1048.574219 78 L 1048.574219 92 L 1041.109375 92 Z M 1041.109375 78 "/> <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(40%,40%,40%);fill-opacity:1;" d="M 1048.574219 78 L 1046.953125 78 L 1046.953125 92 L 1048.574219 92 Z M 1048.574219 78 "/> <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 "/>
<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 430.597656 81 L 430.597656 75 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="432.597656" y="76"/> <use xlink:href="#glyph0-18" x="546.976562" y="122"/>
<use xlink:href="#glyph1-2" x="434.820312" y="76"/> <use xlink:href="#glyph0-7" x="551.681641" y="122"/>
<use xlink:href="#glyph1-3" x="437.363281" y="76"/> <use xlink:href="#glyph0-2" x="556.615234" y="122"/>
<use xlink:href="#glyph1-2" x="444.066406" y="76"/> <use xlink:href="#glyph0-3" x="563.96875" y="122"/>
<use xlink:href="#glyph1-4" x="446.609375" y="76"/> <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> </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 886.390625 81 L 886.390625 75 "/> <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 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <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 "/>
<use xlink:href="#glyph1-1" x="888.390625" y="76"/> <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 "/>
<use xlink:href="#glyph1-2" x="890.613281" y="76"/> <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 "/>
<use xlink:href="#glyph1-3" x="893.15625" y="76"/>
<use xlink:href="#glyph1-2" x="899.859375" y="76"/>
<use xlink:href="#glyph1-5" x="902.402344" y="76"/>
</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 970.753906 81 L 970.753906 75 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="972.753906" y="76"/>
<use xlink:href="#glyph1-2" x="974.976562" y="76"/>
<use xlink:href="#glyph1-3" x="977.519531" y="76"/>
<use xlink:href="#glyph1-2" x="984.222656" y="76"/>
<use xlink:href="#glyph1-6" x="986.765625" y="76"/>
</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 93.207031 46 L 93.207031 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 713.289062 49 L 713.289062 43 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-7" x="715.289062" y="44"/>
<use xlink:href="#glyph1-1" x="719.890625" y="44"/>
<use xlink:href="#glyph1-8" x="722.113281" y="44"/>
<use xlink:href="#glyph1-9" x="725.402344" y="44"/>
<use xlink:href="#glyph1-10" x="729.570312" y="44"/>
<use xlink:href="#glyph1-2" x="732.707031" y="44"/>
<use xlink:href="#glyph1-10" x="735.25" y="44"/>
<use xlink:href="#glyph1-11" x="738.386719" y="44"/>
<use xlink:href="#glyph1-8" x="743.457031" y="44"/>
<use xlink:href="#glyph1-12" x="746.746094" y="44"/>
<use xlink:href="#glyph1-13" x="751.667969" y="44"/>
<use xlink:href="#glyph1-14" x="756.570312" y="44"/>
<use xlink:href="#glyph1-2" x="761.648438" y="44"/>
<use xlink:href="#glyph1-15" x="764.191406" y="44"/>
<use xlink:href="#glyph1-16" x="767.007812" y="44"/>
<use xlink:href="#glyph1-8" x="771.902344" y="44"/>
<use xlink:href="#glyph1-17" x="775.191406" y="44"/>
<use xlink:href="#glyph1-12" x="779.824219" y="44"/>
<use xlink:href="#glyph1-14" x="784.746094" y="44"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 740.554688 110 L 742.976562 110 L 742.976562 124 L 740.554688 124 Z M 740.554688 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 742.976562 110 L 762.324219 110 L 762.324219 124 L 742.976562 124 Z M 742.976562 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 762.328125 110 L 801.253906 110 L 801.253906 124 L 762.328125 124 Z M 762.328125 110 "/>
<g clip-path="url(#clip6)" clip-rule="nonzero"> <g clip-path="url(#clip6)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="764.328125" y="122"/> <use xlink:href="#glyph0-18" x="1018.117188" y="122"/>
<use xlink:href="#glyph0-7" x="769.033203" y="122"/> <use xlink:href="#glyph0-7" x="1022.822266" y="122"/>
<use xlink:href="#glyph0-2" x="773.966797" y="122"/> <use xlink:href="#glyph0-2" x="1027.755859" y="122"/>
<use xlink:href="#glyph0-3" x="781.320312" y="122"/> <use xlink:href="#glyph0-3" x="1035.109375" y="122"/>
<use xlink:href="#glyph0-19" x="787.917969" y="122"/> <use xlink:href="#glyph0-19" x="1041.707031" y="122"/>
<use xlink:href="#glyph0-20" x="795.300781" y="122"/> <use xlink:href="#glyph0-20" x="1049.089844" y="122"/>
<use xlink:href="#glyph0-13" x="798.634766" y="122"/> <use xlink:href="#glyph0-13" x="1052.423828" y="122"/>
</g> </g>
</g> </g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 801.253906 110 L 803.496094 110 L 803.496094 124 L 801.253906 124 Z M 801.253906 110 "/> <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 803.496094 110 L 901.589844 110 L 901.589844 124 L 803.496094 124 Z M 803.496094 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=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 901.589844 110 L 907.84375 110 L 907.84375 124 L 901.589844 124 Z M 901.589844 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 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 907.84375 110 L 939.957031 110 L 939.957031 124 L 907.84375 124 Z M 907.84375 110 "/>
<g clip-path="url(#clip7)" clip-rule="nonzero"> <g clip-path="url(#clip7)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="909.84375" y="122"/> <use xlink:href="#glyph1-1" x="583.375" y="108"/>
<use xlink:href="#glyph0-7" x="914.548828" y="122"/> <use xlink:href="#glyph1-2" x="585.734375" y="108"/>
<use xlink:href="#glyph0-2" x="919.482422" y="122"/> <use xlink:href="#glyph1-3" x="590.628906" y="108"/>
<use xlink:href="#glyph0-3" x="926.835938" y="122"/> <use xlink:href="#glyph1-4" x="595.707031" y="108"/>
<use xlink:href="#glyph0-19" x="933.433594" y="122"/> <use xlink:href="#glyph1-15" x="598.25" y="108"/>
<use xlink:href="#glyph0-20" x="940.816406" y="122"/> <use xlink:href="#glyph1-4" x="603.339844" y="108"/>
<use xlink:href="#glyph0-13" x="944.150391" y="122"/> <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>
</g> </g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 939.957031 110 L 941.289062 110 L 941.289062 124 L 939.957031 124 Z M 939.957031 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 1047.300781 113 L 1047.300781 107 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 941.289062 110 L 984.167969 110 L 984.167969 124 L 941.289062 124 Z M 941.289062 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 984.167969 110 L 1005.09375 110 L 1005.09375 124 L 984.167969 124 Z M 984.167969 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1005.089844 110 L 1022.230469 110 L 1022.230469 124 L 1005.089844 124 Z M 1005.089844 110 "/>
<g clip-path="url(#clip8)" clip-rule="nonzero"> <g clip-path="url(#clip8)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="1007.089844" y="122"/> <use xlink:href="#glyph1-15" x="1049.300781" y="108"/>
<use xlink:href="#glyph0-7" x="1011.794922" y="122"/> <use xlink:href="#glyph1-4" x="1054.390625" y="108"/>
<use xlink:href="#glyph0-2" x="1016.728516" y="122"/> <use xlink:href="#glyph1-13" x="1056.933594" y="108"/>
<use xlink:href="#glyph0-3" x="1024.082031" y="122"/> <use xlink:href="#glyph1-2" x="1062.011719" y="108"/>
<use xlink:href="#glyph0-19" x="1030.679688" y="122"/> <use xlink:href="#glyph1-11" x="1066.90625" y="108"/>
<use xlink:href="#glyph0-20" x="1038.0625" y="122"/> <use xlink:href="#glyph1-14" x="1071.976562" y="108"/>
<use xlink:href="#glyph0-13" x="1041.396484" y="122"/>
</g> </g>
</g> </g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1022.230469 110 L 1023.820312 110 L 1023.820312 124 L 1022.230469 124 Z M 1022.230469 110 "/> <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%,40%,40%);fill-opacity:1;" d="M 1023.820312 110 L 1048.574219 110 L 1048.574219 124 L 1023.820312 124 Z M 1023.820312 110 "/> <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 1048.574219 110 L 1053.917969 110 L 1053.917969 124 L 1048.574219 124 Z M 1048.574219 110 "/> <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(40%,40%,40%);fill-opacity:1;" d="M 1053.917969 110 L 1052.722656 110 L 1052.722656 124 L 1053.917969 124 Z M 1053.917969 110 "/> <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 "/>
<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 787.792969 113 L 787.792969 107 "/> <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 clip-path="url(#clip9)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-18" x="789.792969" y="108"/> <use xlink:href="#glyph0-18" x="1082.304688" y="154"/>
<use xlink:href="#glyph1-2" x="792.015625" y="108"/> <use xlink:href="#glyph0-7" x="1087.009766" y="154"/>
<use xlink:href="#glyph1-3" x="794.558594" y="108"/> <use xlink:href="#glyph0-2" x="1091.943359" y="154"/>
<use xlink:href="#glyph1-2" x="801.261719" y="108"/> <use xlink:href="#glyph0-3" x="1099.296875" y="154"/>
<use xlink:href="#glyph1-4" x="803.804688" y="108"/> <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>
</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 929.160156 113 L 929.160156 107 "/> <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 clip-path="url(#clip10)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-18" x="931.160156" y="108"/> <use xlink:href="#glyph1-1" x="819.433594" y="140"/>
<use xlink:href="#glyph1-2" x="933.382812" y="108"/> <use xlink:href="#glyph1-2" x="821.792969" y="140"/>
<use xlink:href="#glyph1-3" x="935.925781" y="108"/> <use xlink:href="#glyph1-3" x="826.6875" y="140"/>
<use xlink:href="#glyph1-2" x="942.628906" y="108"/> <use xlink:href="#glyph1-4" x="831.765625" y="140"/>
<use xlink:href="#glyph1-5" x="945.171875" y="108"/> <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>
</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 1011.980469 113 L 1011.980469 107 "/> <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 clip-path="url(#clip11)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-18" x="1013.980469" y="108"/> <use xlink:href="#glyph1-16" x="1111.605469" y="140"/>
<use xlink:href="#glyph1-2" x="1016.203125" y="108"/> <use xlink:href="#glyph1-4" x="1116.695312" y="140"/>
<use xlink:href="#glyph1-3" x="1018.746094" y="108"/> <use xlink:href="#glyph1-13" x="1119.238281" y="140"/>
<use xlink:href="#glyph1-2" x="1025.449219" y="108"/> <use xlink:href="#glyph1-2" x="1124.316406" y="140"/>
<use xlink:href="#glyph1-6" x="1027.992188" y="108"/> <use xlink:href="#glyph1-11" x="1129.210938" y="140"/>
<use xlink:href="#glyph1-14" x="1134.28125" y="140"/>
</g> </g>
</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 740.554688 46 L 740.554688 124 "/> <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 834.167969 49 L 834.167969 43 "/> <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 clip-path="url(#clip12)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-19" x="836.167969" y="44"/> <use xlink:href="#glyph1-17" x="901.039062" y="44"/>
<use xlink:href="#glyph1-12" x="841.246094" y="44"/> <use xlink:href="#glyph1-18" x="906.511719" y="44"/>
<use xlink:href="#glyph1-20" x="846.167969" y="44"/> <use xlink:href="#glyph1-18" x="908.734375" y="44"/>
<use xlink:href="#glyph1-16" x="850.566406" y="44"/> <use xlink:href="#glyph1-4" x="910.957031" y="44"/>
<use xlink:href="#glyph1-21" x="855.460938" y="44"/> <use xlink:href="#glyph1-19" x="913.5" y="44"/>
<use xlink:href="#glyph1-14" x="860.53125" y="44"/> <use xlink:href="#glyph1-20" x="917.898438" y="44"/>
<use xlink:href="#glyph1-2" x="865.609375" y="44"/> <use xlink:href="#glyph1-10" x="922.96875" y="44"/>
<use xlink:href="#glyph1-10" x="868.152344" y="44"/> <use xlink:href="#glyph1-18" x="925.191406" y="44"/>
<use xlink:href="#glyph1-11" x="871.289062" y="44"/> <use xlink:href="#glyph1-13" x="927.414062" y="44"/>
<use xlink:href="#glyph1-8" x="876.359375" y="44"/> <use xlink:href="#glyph1-4" x="932.492188" y="44"/>
<use xlink:href="#glyph1-12" x="879.648438" y="44"/> <use xlink:href="#glyph1-21" x="935.035156" y="44"/>
<use xlink:href="#glyph1-13" x="884.570312" y="44"/> <use xlink:href="#glyph1-10" x="937.851562" y="44"/>
<use xlink:href="#glyph1-14" x="889.472656" y="44"/> <use xlink:href="#glyph1-3" x="940.074219" y="44"/>
<use xlink:href="#glyph1-2" x="894.550781" y="44"/> <use xlink:href="#glyph1-14" x="945.152344" y="44"/>
<use xlink:href="#glyph1-15" x="897.09375" y="44"/> <use xlink:href="#glyph1-9" x="950.074219" y="44"/>
<use xlink:href="#glyph1-16" x="899.910156" y="44"/> <use xlink:href="#glyph1-6" x="953.363281" y="44"/>
<use xlink:href="#glyph1-8" x="904.804688" y="44"/> <use xlink:href="#glyph1-4" x="957.53125" y="44"/>
<use xlink:href="#glyph1-17" x="908.09375" y="44"/> <use xlink:href="#glyph1-21" x="960.074219" y="44"/>
<use xlink:href="#glyph1-12" x="912.726562" y="44"/> <use xlink:href="#glyph1-2" x="962.890625" y="44"/>
<use xlink:href="#glyph1-14" x="917.648438" y="44"/> <use xlink:href="#glyph1-9" x="967.785156" y="44"/>
<use xlink:href="#glyph1-22" x="922.726562" y="44"/> <use xlink:href="#glyph1-22" x="971.074219" y="44"/>
<use xlink:href="#glyph1-2" x="925.421875" y="44"/> <use xlink:href="#glyph1-14" x="975.707031" y="44"/>
<use xlink:href="#glyph1-10" x="927.964844" y="44"/> <use xlink:href="#glyph1-13" x="980.628906" y="44"/>
<use xlink:href="#glyph1-16" x="931.101562" y="44"/>
<use xlink:href="#glyph1-23" x="935.996094" y="44"/>
<use xlink:href="#glyph1-24" x="941.074219" y="44"/>
<use xlink:href="#glyph1-25" x="943.960938" y="44"/>
<use xlink:href="#glyph1-12" x="946.183594" y="44"/>
<use xlink:href="#glyph1-26" x="951.105469" y="44"/>
<use xlink:href="#glyph1-12" x="955.839844" y="44"/>
<use xlink:href="#glyph1-25" x="960.761719" y="44"/>
<use xlink:href="#glyph1-2" x="962.984375" y="44"/>
<use xlink:href="#glyph1-20" x="965.527344" y="44"/>
<use xlink:href="#glyph1-16" x="969.925781" y="44"/>
<use xlink:href="#glyph1-14" x="974.820312" y="44"/>
<use xlink:href="#glyph1-12" x="979.898438" y="44"/>
<use xlink:href="#glyph1-2" x="984.820312" y="44"/>
<use xlink:href="#glyph1-1" x="987.363281" y="44"/>
<use xlink:href="#glyph1-9" x="989.585938" y="44"/>
<use xlink:href="#glyph1-2" x="993.753906" y="44"/>
<use xlink:href="#glyph1-15" x="996.296875" y="44"/>
<use xlink:href="#glyph1-1" x="999.113281" y="44"/>
<use xlink:href="#glyph1-21" x="1001.335938" y="44"/>
<use xlink:href="#glyph1-1" x="1006.40625" y="44"/>
<use xlink:href="#glyph1-9" x="1008.628906" y="44"/>
<use xlink:href="#glyph1-11" x="1012.796875" y="44"/>
<use xlink:href="#glyph1-12" x="1017.867188" y="44"/>
<use xlink:href="#glyph1-14" x="1022.789062" y="44"/>
</g> </g>
</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 35.601562 36 L 31.601562 36 L 31.601562 128 L 35.601562 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 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 1057.957031 36 L 1061.957031 36 L 1061.957031 128 L 1057.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 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;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-9" x="33.601562" y="44"/> <use xlink:href="#glyph1-6" x="44.675781" y="44"/>
<use xlink:href="#glyph1-27" x="37.769531" y="44"/> <use xlink:href="#glyph1-23" x="48.84375" y="44"/>
<use xlink:href="#glyph1-1" x="44.3125" y="44"/> <use xlink:href="#glyph1-10" x="55.386719" y="44"/>
<use xlink:href="#glyph1-10" x="46.535156" y="44"/> <use xlink:href="#glyph1-7" x="57.609375" y="44"/>
<use xlink:href="#glyph1-20" x="49.671875" y="44"/> <use xlink:href="#glyph1-19" x="60.746094" y="44"/>
<use xlink:href="#glyph1-11" x="54.070312" y="44"/> <use xlink:href="#glyph1-20" x="65.144531" 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 1086.320312 49 L 1086.320312 43 "/> <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 clip-path="url(#clip13)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> <g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-19" x="1088.320312" y="44"/> <use xlink:href="#glyph1-24" x="1179.296875" y="44"/>
<use xlink:href="#glyph1-27" x="1093.398438" y="44"/> <use xlink:href="#glyph1-23" x="1184.375" y="44"/>
<use xlink:href="#glyph1-1" x="1099.941406" y="44"/> <use xlink:href="#glyph1-10" x="1190.917969" y="44"/>
<use xlink:href="#glyph1-10" x="1102.164062" y="44"/> <use xlink:href="#glyph1-7" x="1193.140625" y="44"/>
<use xlink:href="#glyph1-20" x="1105.300781" y="44"/> <use xlink:href="#glyph1-19" x="1196.277344" y="44"/>
<use xlink:href="#glyph1-11" x="1109.699219" y="44"/> <use xlink:href="#glyph1-20" x="1200.675781" y="44"/>
<use xlink:href="#glyph1-2" x="1114.769531" y="44"/> <use xlink:href="#glyph1-4" x="1205.746094" y="44"/>
<use xlink:href="#glyph1-1" x="1117.3125" y="44"/> <use xlink:href="#glyph1-10" x="1208.289062" y="44"/>
<use xlink:href="#glyph1-9" x="1119.535156" y="44"/> <use xlink:href="#glyph1-6" x="1210.511719" y="44"/>
<use xlink:href="#glyph1-2" x="1123.703125" y="44"/> <use xlink:href="#glyph1-4" x="1214.679688" y="44"/>
<use xlink:href="#glyph1-15" x="1126.246094" y="44"/> <use xlink:href="#glyph1-21" x="1217.222656" y="44"/>
<use xlink:href="#glyph1-1" x="1129.0625" y="44"/> <use xlink:href="#glyph1-10" x="1220.039062" y="44"/>
<use xlink:href="#glyph1-21" x="1131.285156" y="44"/> <use xlink:href="#glyph1-11" x="1222.261719" y="44"/>
<use xlink:href="#glyph1-1" x="1136.355469" y="44"/> <use xlink:href="#glyph1-10" x="1227.332031" y="44"/>
<use xlink:href="#glyph1-9" x="1138.578125" y="44"/> <use xlink:href="#glyph1-6" x="1229.554688" y="44"/>
<use xlink:href="#glyph1-11" x="1142.746094" y="44"/> <use xlink:href="#glyph1-20" x="1233.722656" y="44"/>
<use xlink:href="#glyph1-12" x="1147.816406" y="44"/> <use xlink:href="#glyph1-14" x="1238.792969" y="44"/>
<use xlink:href="#glyph1-14" x="1152.738281" y="44"/> <use xlink:href="#glyph1-13" x="1243.714844" y="44"/>
</g> </g>
</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 11.480469 46 L 11.480469 60 "/> <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> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -1,6 +1,7 @@
(lang dune 3.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)
@ -13,7 +14,7 @@
(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))) (conflicts (seq (< 0.3)))
(depends (depends
(ocaml (>= 5.1.0)) (ocaml (>= 5.2.0))
(bigstringaf (>= 0.9.0)) (bigstringaf (>= 0.9.0))
(cstruct (>= 6.0.1)) (cstruct (>= 6.0.1))
lwt-dllist lwt-dllist
@ -24,7 +25,7 @@
(domain-local-await (>= 0.1.0)) (domain-local-await (>= 0.1.0))
(crowbar (and (>= 0.2) :with-test)) (crowbar (and (>= 0.2) :with-test))
(mtime (>= 2.0.0)) (mtime (>= 2.0.0))
(mdx (and (>= 2.2.0) (< 2.4.0) :with-test)) (mdx (and (>= 2.4.1) :with-test))
(dscheck (and (>= 0.1.0) :with-test)))) (dscheck (and (>= 0.1.0) :with-test))))
(package (package
(name eio_linux) (name eio_linux)
@ -34,11 +35,11 @@
(depends (depends
(alcotest (and (>= 1.7.0) :with-test)) (alcotest (and (>= 1.7.0) :with-test))
(eio (= :version)) (eio (= :version))
(mdx (and (>= 2.2.0) :with-test)) (mdx (and (>= 2.4.1) :with-test))
(logs (and (>= 0.7.0) :with-test)) (logs (and (>= 0.7.0) :with-test))
(fmt (>= 0.8.9)) (fmt (>= 0.8.9))
(cmdliner (and (>= 1.1.0) :with-test)) (cmdliner (and (>= 1.1.0) :with-test))
(uring (>= 0.7)))) (uring (>= 0.9))))
(package (package
(name eio_posix) (name eio_posix)
(allow_empty) ; Work-around for dune bug #6938 (allow_empty) ; Work-around for dune bug #6938
@ -47,7 +48,8 @@
(depends (depends
(eio (= :version)) (eio (= :version))
(iomux (>= 0.2)) (iomux (>= 0.2))
(mdx (and (>= 2.2.0) :with-test)) (mdx (and (>= 2.4.1) :with-test))
(conf-bash :with-test)
(fmt (>= 0.8.9)))) (fmt (>= 0.8.9))))
(package (package
(name eio_windows) (name eio_windows)
@ -64,7 +66,7 @@
(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
(mdx (and (>= 2.2.0) :with-test)) (mdx (and (>= 2.4.1) :with-test))
(kcas (and (>= 0.3.0) :with-test)) (kcas (and (>= 0.3.0) :with-test))
(yojson (and (>= 2.0.2) :with-test)) (yojson (and (>= 2.0.2) :with-test))
(eio_linux (and (eio_linux (and

View File

@ -10,7 +10,7 @@ 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" {>= "3.9"} "dune" {>= "3.9"}
"ocaml" {>= "5.1.0"} "ocaml" {>= "5.2.0"}
"bigstringaf" {>= "0.9.0"} "bigstringaf" {>= "0.9.0"}
"cstruct" {>= "6.0.1"} "cstruct" {>= "6.0.1"}
"lwt-dllist" "lwt-dllist"
@ -21,7 +21,7 @@ depends: [
"domain-local-await" {>= "0.1.0"} "domain-local-await" {>= "0.1.0"}
"crowbar" {>= "0.2" & with-test} "crowbar" {>= "0.2" & with-test}
"mtime" {>= "2.0.0"} "mtime" {>= "2.0.0"}
"mdx" {>= "2.2.0" & < "2.4.0" & with-test} "mdx" {>= "2.4.1" & with-test}
"dscheck" {>= "0.1.0" & with-test} "dscheck" {>= "0.1.0" & with-test}
"odoc" {with-doc} "odoc" {with-doc}
] ]
@ -29,7 +29,6 @@ conflicts: [
"seq" {< "0.3"} "seq" {< "0.3"}
] ]
build: [ build: [
["dune" "subst"] {dev}
[ [
"dune" "dune"
"build" "build"

View File

@ -12,15 +12,14 @@ depends: [
"dune" {>= "3.9"} "dune" {>= "3.9"}
"alcotest" {>= "1.7.0" & with-test} "alcotest" {>= "1.7.0" & with-test}
"eio" {= version} "eio" {= version}
"mdx" {>= "2.2.0" & with-test} "mdx" {>= "2.4.1" & with-test}
"logs" {>= "0.7.0" & with-test} "logs" {>= "0.7.0" & with-test}
"fmt" {>= "0.8.9"} "fmt" {>= "0.8.9"}
"cmdliner" {>= "1.1.0" & with-test} "cmdliner" {>= "1.1.0" & with-test}
"uring" {>= "0.7"} "uring" {>= "0.9"}
"odoc" {with-doc} "odoc" {with-doc}
] ]
build: [ build: [
["dune" "subst"] {dev}
[ [
"dune" "dune"
"build" "build"

View File

@ -10,7 +10,7 @@ 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" {>= "3.9"} "dune" {>= "3.9"}
"mdx" {>= "2.2.0" & with-test} "mdx" {>= "2.4.1" & with-test}
"kcas" {>= "0.3.0" & with-test} "kcas" {>= "0.3.0" & with-test}
"yojson" {>= "2.0.2" & with-test} "yojson" {>= "2.0.2" & with-test}
"eio_linux" "eio_linux"
@ -21,7 +21,6 @@ depends: [
"odoc" {with-doc} "odoc" {with-doc}
] ]
build: [ build: [
["dune" "subst"] {dev}
[ [
"dune" "dune"
"build" "build"

View File

@ -12,12 +12,12 @@ depends: [
"dune" {>= "3.9"} "dune" {>= "3.9"}
"eio" {= version} "eio" {= version}
"iomux" {>= "0.2"} "iomux" {>= "0.2"}
"mdx" {>= "2.2.0" & with-test} "mdx" {>= "2.4.1" & with-test}
"conf-bash" {with-test}
"fmt" {>= "0.8.9"} "fmt" {>= "0.8.9"}
"odoc" {with-doc} "odoc" {with-doc}
] ]
build: [ build: [
["dune" "subst"] {dev}
[ [
"dune" "dune"
"build" "build"

View File

@ -17,7 +17,6 @@ depends: [
"odoc" {with-doc} "odoc" {with-doc}
] ]
build: [ build: [
["dune" "subst"] {dev}
[ [
"dune" "dune"
"build" "build"

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

View File

@ -218,6 +218,13 @@ val seq : ?stop:bool parser -> 'a parser -> 'a Seq.t parser
It is not necessary to consume all the elements of the It is not necessary to consume all the elements of the
sequence. 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. @param stop This is used before parsing each item.
The sequence ends if this returns [true]. The sequence ends if this returns [true].
The default is {!at_end_of_input}. *) The default is {!at_end_of_input}. *)

View File

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

View File

@ -7,6 +7,7 @@ module Private = struct
module Suspend = Suspend module Suspend = Suspend
module Cells = Cells module Cells = Cells
module Broadcast = Broadcast module Broadcast = Broadcast
module Single_waiter = Single_waiter
module Trace = Trace module Trace = Trace
module Fiber_context = Cancel.Fiber_context module Fiber_context = Cancel.Fiber_context
module Debug = Debug module Debug = Debug
@ -18,6 +19,4 @@ module Private = struct
| Fork = Fiber.Fork | Fork = Fiber.Fork
| Get_context = Cancel.Get_context | Get_context = Cancel.Get_context
end end
module Dla = Dla
end end

View File

@ -606,6 +606,7 @@ module Private : sig
module Cells = Cells module Cells = Cells
module Broadcast = Broadcast module Broadcast = Broadcast
module Single_waiter = Single_waiter
(** Every fiber has an associated context. *) (** Every fiber has an associated context. *)
module Fiber_context : sig module Fiber_context : sig
@ -781,8 +782,4 @@ module Private : sig
val v : t val v : t
(** Backends should use this for {!Eio.Stdenv.debug}. *) (** Backends should use this for {!Eio.Stdenv.debug}. *)
end end
module Dla : sig
val prepare_for_await : unit -> Domain_local_await.t
end
end end

View File

@ -39,7 +39,8 @@ let fork_daemon ~sw f =
(* The daemon was cancelled because all non-daemon fibers are finished. *) (* The daemon was cancelled because all non-daemon fibers are finished. *)
() ()
| exception ex -> | exception ex ->
Switch.fail sw ex; (* The [with_daemon] ensures this will succeed *) 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 *) ) (* else the fiber should report the error to [sw], but [sw] is failed anyway *)
let fork_promise ~sw f = let fork_promise ~sw f =
@ -65,7 +66,8 @@ let fork_promise_exn ~sw f =
match Switch.with_op sw f with match Switch.with_op sw f with
| x -> Promise.resolve r x | x -> Promise.resolve r x
| exception ex -> | exception ex ->
Switch.fail sw ex (* The [with_op] ensures this will succeed *) let bt = Printexc.get_raw_backtrace () in
Switch.fail ~bt sw ex (* The [with_op] ensures this will succeed *)
); );
p p
@ -226,7 +228,7 @@ module List = struct
let release t = let release t =
t.free_fibers <- t.free_fibers + 1; t.free_fibers <- t.free_fibers + 1;
if t.free_fibers = 1 then Single_waiter.wake t.cond (Ok ()) if t.free_fibers = 1 then Single_waiter.wake_if_sleeping t.cond
let use t fn x = let use t fn x =
await_free t; await_free t;

View File

@ -1,25 +1,32 @@
(* Allows a single fiber to wait to be notified by another fiber in the same domain. type 'a state =
If multiple fibers need to wait at once, or the notification comes from another domain, | Running
this can't be used. *) | Sleeping of (('a, exn) result -> unit)
type 'a t = { type 'a t = 'a state ref
mutable wake : ('a, exn) result -> unit;
}
let create () = { wake = ignore } let create () = ref Running
let wake t v = t.wake v 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 await t op id =
let x = let x =
Suspend.enter op @@ fun ctx enqueue -> Suspend.enter op @@ fun ctx enqueue ->
Cancel.Fiber_context.set_cancel_fn ctx (fun ex -> Cancel.Fiber_context.set_cancel_fn ctx (fun ex ->
t.wake <- ignore; t := Running;
enqueue (Error ex) enqueue (Error ex)
); );
t.wake <- (fun x -> t := Sleeping (fun x ->
Cancel.Fiber_context.clear_cancel_fn ctx; Cancel.Fiber_context.clear_cancel_fn ctx;
t.wake <- ignore; t := Running;
enqueue x enqueue x
) )
in in
@ -29,7 +36,7 @@ let await t op id =
let await_protect t op id = let await_protect t op id =
let x = let x =
Suspend.enter_unchecked op @@ fun _ctx enqueue -> Suspend.enter_unchecked op @@ fun _ctx enqueue ->
t.wake <- (fun x -> t.wake <- ignore; enqueue x) t := Sleeping (fun x -> t := Running; enqueue x)
in in
Trace.get id; Trace.get id;
x 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

@ -72,7 +72,7 @@ let dec_fibers t =
if t.daemon_fibers > 0 && t.fibers = t.daemon_fibers then if t.daemon_fibers > 0 && t.fibers = t.daemon_fibers then
Cancel.cancel t.cancel Exit; Cancel.cancel t.cancel Exit;
if t.fibers = 0 then if t.fibers = 0 then
Single_waiter.wake t.waiter (Ok ()) Single_waiter.wake_if_sleeping t.waiter
let with_op t fn = let with_op t fn =
inc_fibers t; inc_fibers t;

View File

@ -70,6 +70,7 @@ module Pi = struct
val rmdir : t -> path -> unit val rmdir : t -> path -> unit
val rename : t -> path -> _ dir -> path -> unit val rename : t -> path -> _ dir -> path -> unit
val read_link : t -> path -> string val read_link : t -> path -> string
val symlink : link_to:path -> t -> path -> unit
val pp : t Fmt.t val pp : t Fmt.t
val native : t -> string -> string option val native : t -> string -> string option
end end

View File

@ -105,7 +105,7 @@ let run_full main =
let result = ref None in let result = ref None in
let `Exit_scheduler = let `Exit_scheduler =
Domain_local_await.using Domain_local_await.using
~prepare_for_await:Eio.Private.Dla.prepare_for_await ~prepare_for_await:Eio_utils.Dla.prepare_for_await
~while_running:(fun () -> ~while_running:(fun () ->
fork ~new_fiber (fun () -> result := Some (main stdenv))) in fork ~new_fiber (fun () -> result := Some (main stdenv))) in
match !result with match !result with

View File

@ -231,6 +231,9 @@ val run_server :
In such cases you must ensure that [connection_handler] only accesses thread-safe values. In such cases you must ensure that [connection_handler] only accesses thread-safe values.
Note that having more than {!Domain.recommended_domain_count} domains in total is likely to result in bad performance. Note that having more than {!Domain.recommended_domain_count} domains in total is likely to result in bad performance.
For services that are bottlenecked on CPU rather than IO,
you can run a single accept loop and have the handler submit CPU-intensive jobs to an {!module:Executor_pool}.
@param max_connections The maximum number of concurrent connections accepted by [s] at any time. @param max_connections The maximum number of concurrent connections accepted by [s] at any time.
The default is [Int.max_int]. The default is [Int.max_int].
@param stop Resolving this promise causes [s] to stop accepting new connections. @param stop Resolving this promise causes [s] to stop accepting new connections.

View File

@ -1,11 +1,22 @@
type 'a t = 'a Fs.dir * Fs.path type 'a t = 'a Fs.dir * Fs.path
(* Like [Filename.is_relative] but always using "/" as the separator. *)
let is_relative = function
| "" -> true
| x -> x.[0] <> '/'
(* Like [Filename.concat] but always using "/" as the separator. *)
let concat a b =
let l = String.length a in
if l = 0 || a.[l - 1] = '/' then a ^ b
else a ^ "/" ^ b
let ( / ) (dir, p1) p2 = let ( / ) (dir, p1) p2 =
match p1, p2 with match p1, p2 with
| p1, "" -> (dir, Filename.concat p1 p2) | p1, "" -> (dir, concat p1 p2)
| _, p2 when not (Filename.is_relative p2) -> (dir, p2) | _, p2 when not (is_relative p2) -> (dir, p2)
| ".", p2 -> (dir, p2) | ".", p2 -> (dir, p2)
| p1, p2 -> (dir, Filename.concat p1 p2) | p1, p2 -> (dir, concat p1 p2)
let pp f (Resource.T (t, ops), p) = let pp f (Resource.T (t, ops), p) =
let module X = (val (Resource.get ops Fs.Pi.Dir)) in let module X = (val (Resource.get ops Fs.Pi.Dir)) in
@ -199,6 +210,14 @@ let rename t1 t2 =
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "renaming %a to %a" pp t1 pp t2 Exn.reraise_with_context ex bt "renaming %a to %a" pp t1 pp t2
let symlink ~link_to source =
let (Resource.T (dir, ops), path) = source in
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try X.symlink dir path ~link_to
with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "creating symlink %a -> %s" pp source link_to
let rec mkdirs ?(exists_ok=false) ~perm t = let rec mkdirs ?(exists_ok=false) ~perm t =
(* Check parent exists first. *) (* Check parent exists first. *)
split t |> Option.iter (fun (parent, _) -> split t |> Option.iter (fun (parent, _) ->

View File

@ -207,3 +207,13 @@ val rename : _ t -> _ t -> unit
(** [rename old_t new_t] atomically unlinks [old_t] and links it as [new_t]. (** [rename old_t new_t] atomically unlinks [old_t] and links it as [new_t].
If [new_t] already exists, it is atomically replaced. *) If [new_t] already exists, it is atomically replaced. *)
val symlink : link_to:string -> _ t -> unit
(** [symlink ~link_to t] creates a symbolic link [t] to [link_to].
[t] is the symlink that is created and [link_to] is the name used in the link.
For example, this creates a "current" symlink pointing at "version-1.0":
{[
Eio.Path.symlink (dir / "current") ~link_to:"version-1.0"
]} *)

View File

@ -89,11 +89,10 @@ let cancel segment cell =
| In_transition | Resource _ -> assert false (* Can't get here from [Request]. *) | In_transition | Resource _ -> assert false (* Can't get here from [Request]. *)
(* If [t] is under capacity, add another (empty) slot. *) (* If [t] is under capacity, add another (empty) slot. *)
let rec maybe_add_slot t = let rec maybe_add_slot t current =
let current = Atomic.get t.slots in
if current < t.max_slots then ( if current < t.max_slots then (
if Atomic.compare_and_set t.slots current (current + 1) then add t (ref None) if Atomic.compare_and_set t.slots current (current + 1) then add t (ref None)
else maybe_add_slot t (* Concurrent update; try again *) else maybe_add_slot t (Atomic.get t.slots) (* Concurrent update; try again *)
) )
(* [run_with t f slot] ensures that [slot] contains a valid resource and then runs [f resource] with it. (* [run_with t f slot] ensures that [slot] contains a valid resource and then runs [f resource] with it.
@ -122,7 +121,19 @@ let run_with t f slot =
add t slot; add t slot;
Printexc.raise_with_backtrace ex bt Printexc.raise_with_backtrace ex bt
let use t f = (* Creates a fresh resource [x], runs [f x], then disposes of [x] *)
let run_new_and_dispose t f =
let x = t.alloc () in
match f x with
| r ->
t.dispose x;
r
| exception ex ->
let bt = Printexc.get_raw_backtrace () in
t.dispose x;
Printexc.raise_with_backtrace ex bt
let use t ?(never_block=false) f =
let segment, cell = Q.next_suspend t.q in let segment, cell = Q.next_suspend t.q in
match Atomic.get cell with match Atomic.get cell with
| Finished | Request _ -> assert false | Finished | Request _ -> assert false
@ -130,9 +141,18 @@ let use t f =
Atomic.set cell Finished; (* Allow value to be GC'd *) Atomic.set cell Finished; (* Allow value to be GC'd *)
run_with t f slot run_with t f slot
| In_transition -> | In_transition ->
(* It would have been better if more resources were available. let current = Atomic.get t.slots in
If we still have capacity, add a new slot now. *) match current < t.max_slots with
maybe_add_slot t; | false when never_block -> (
(* We are at capacity, but cannot block.
Create a new resource to run f but don't add it to the pool. *)
match Atomic.exchange cell Finished with
| Resource slot -> run_with t f slot
| _ -> run_new_and_dispose t f
)
| can_add ->
(* Create a slot if not at capacity. *)
if can_add then maybe_add_slot t current;
(* No item is available right now. Start waiting *) (* No item is available right now. Start waiting *)
let slot = let slot =
Suspend.enter_unchecked "Pool.acquire" (fun ctx enqueue -> Suspend.enter_unchecked "Pool.acquire" (fun ctx enqueue ->

View File

@ -35,6 +35,12 @@ val create :
If it raises, the exception is passed on to the user, If it raises, the exception is passed on to the user,
but resource is still considered to have been disposed. *) but resource is still considered to have been disposed. *)
val use : 'a t -> ('a -> 'b) -> 'b val use : 'a t -> ?never_block:bool -> ('a -> 'b) -> 'b
(** [use t fn] waits for some resource [x] to be available and then runs [f x]. (** [use t fn] waits for some resource [x] to be available and then runs [f x].
Afterwards (on success or error), [x] is returned to the pool. *) Afterwards (on success or error), [x] is returned to the pool.
@param never_block If [true] and the pool has reached maximum capacity,
then a fresh resource is created to ensure that this [use]
call does not wait for a resource to become available.
This resource is immediately disposed after [f x] returns.
*)

View File

@ -1,3 +1,7 @@
type error = ECONNRESET
exception Unix_error of error * string * string
type file_descr = [`Open | `Closed] Atomic.t type file_descr = [`Open | `Closed] Atomic.t
let make () = Atomic.make `Open let make () = Atomic.make `Open

View File

@ -87,6 +87,11 @@ module Timeout = struct
| Timeout (clock, d) -> | Timeout (clock, d) ->
Fiber.first (fun () -> Mono.sleep_span clock d; raise Timeout) fn Fiber.first (fun () -> Mono.sleep_span clock d; raise Timeout) fn
let sleep t =
match t with
| Unlimited -> Fiber.await_cancel ()
| Timeout (clock, d) -> Mono.sleep_span clock d
let pp_duration f d = let pp_duration f d =
if d >= 0.001 && d < 0.1 then if d >= 0.001 && d < 0.1 then
Fmt.pf f "%.2gms" (d *. 1000.) Fmt.pf f "%.2gms" (d *. 1000.)

View File

@ -70,6 +70,9 @@ module Timeout : sig
(** [run_exn t fn] runs [fn ()] but cancels it if it takes longer than allowed by timeout [t], (** [run_exn t fn] runs [fn ()] but cancels it if it takes longer than allowed by timeout [t],
raising exception {!exception-Timeout}. *) raising exception {!exception-Timeout}. *)
val sleep : t -> unit
(** [sleep t] sleeps for [t]'s duration. *)
val pp : t Fmt.t val pp : t Fmt.t
(** [pp] formats a timeout as a duration (e.g. "5s"). (** [pp] formats a timeout as a duration (e.g. "5s").
This is intended for use in error messages and logging and is rounded. *) This is intended for use in error messages and logging and is rounded. *)

View File

@ -1,6 +1,7 @@
(library (library
(name eio_unix) (name eio_unix)
(public_name eio.unix) (public_name eio.unix)
(public_headers include/fork_action.h)
(foreign_stubs (foreign_stubs
(language c) (language c)
(include_dirs include) (include_dirs include)

View File

@ -61,18 +61,23 @@ type t = [`Generic | `Unix] Eio.Net.ty r
type _ Effect.t += type _ Effect.t +=
| Import_socket_stream : Switch.t * bool * Unix.file_descr -> [`Unix_fd | stream_socket_ty] r Effect.t | Import_socket_stream : Switch.t * bool * Unix.file_descr -> [`Unix_fd | stream_socket_ty] r Effect.t
| Import_socket_listening : Switch.t * bool * Unix.file_descr -> [`Unix_fd | listening_socket_ty] r Effect.t
| Import_socket_datagram : Switch.t * bool * Unix.file_descr -> [`Unix_fd | datagram_socket_ty] r Effect.t | Import_socket_datagram : Switch.t * bool * Unix.file_descr -> [`Unix_fd | datagram_socket_ty] r Effect.t
| Socketpair_stream : Switch.t * Unix.socket_domain * int -> | Socketpair_stream : Switch.t * Unix.socket_domain * int ->
([`Unix_fd | stream_socket_ty] r * [`Unix_fd | stream_socket_ty] r) Effect.t ([`Unix_fd | stream_socket_ty] r * [`Unix_fd | stream_socket_ty] r) Effect.t
| Socketpair_datagram : Switch.t * Unix.socket_domain * int -> | Socketpair_datagram : Switch.t * Unix.socket_domain * int ->
([`Unix_fd | datagram_socket_ty] r * [`Unix_fd | datagram_socket_ty] r) Effect.t ([`Unix_fd | datagram_socket_ty] r * [`Unix_fd | datagram_socket_ty] r) Effect.t
let open_stream s = (s : _ stream_socket :> [< `Unix_fd | stream_socket_ty] r) let open_stream s = (s : [`Unix_fd | stream_socket_ty] r :> [< `Unix_fd | stream_socket_ty] r)
let open_datagram s = (s : _ datagram_socket :> [< `Unix_fd | datagram_socket_ty] r) let open_listening s = (s : [`Unix_fd | listening_socket_ty] r :> [< `Unix_fd | listening_socket_ty] r)
let open_datagram s = (s : [`Unix_fd | datagram_socket_ty] r :> [< `Unix_fd | datagram_socket_ty] r)
let import_socket_stream ~sw ~close_unix fd = let import_socket_stream ~sw ~close_unix fd =
open_stream @@ Effect.perform (Import_socket_stream (sw, close_unix, fd)) open_stream @@ Effect.perform (Import_socket_stream (sw, close_unix, fd))
let import_socket_listening ~sw ~close_unix fd =
open_listening @@ Effect.perform (Import_socket_listening (sw, close_unix, fd))
let import_socket_datagram ~sw ~close_unix fd = let import_socket_datagram ~sw ~close_unix fd =
open_datagram @@ Effect.perform (Import_socket_datagram (sw, close_unix, fd)) open_datagram @@ Effect.perform (Import_socket_datagram (sw, close_unix, fd))
@ -81,7 +86,8 @@ let socketpair_stream ~sw ?(domain=Unix.PF_UNIX) ?(protocol=0) () =
(open_stream a, open_stream b) (open_stream a, open_stream b)
let socketpair_datagram ~sw ?(domain=Unix.PF_UNIX) ?(protocol=0) () = let socketpair_datagram ~sw ?(domain=Unix.PF_UNIX) ?(protocol=0) () =
Effect.perform (Socketpair_datagram (sw, domain, protocol)) let a, b = Effect.perform (Socketpair_datagram (sw, domain, protocol)) in
(open_datagram a, open_datagram b)
let fd socket = let fd socket =
Option.get (Resource.fd_opt socket) Option.get (Resource.fd_opt socket)

View File

@ -55,16 +55,23 @@ end
(** {2 Creating or importing sockets} *) (** {2 Creating or importing sockets} *)
val import_socket_stream : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [`Unix_fd | stream_socket_ty] r val import_socket_stream : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [< `Unix_fd | stream_socket_ty] r
(** [import_socket_stream ~sw ~close_unix:true fd] is an Eio flow that uses [fd]. (** [import_socket_stream ~sw ~close_unix fd] is an Eio flow that uses [fd].
It can be cast to e.g. {!source} for a one-way flow. It can be cast to e.g. {!source} for a one-way flow.
The socket object will be closed when [sw] finishes. The socket object will be closed when [sw] finishes.
The [close_unix] and [sw] arguments are passed to {!Fd.of_unix}. *) The [close_unix] and [sw] arguments are passed to {!Fd.of_unix}. *)
val import_socket_datagram : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [`Unix_fd | datagram_socket_ty] r val import_socket_listening : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [< `Unix_fd | listening_socket_ty] r
(** [import_socket_datagram ~sw ~close_unix:true fd] is an Eio datagram socket that uses [fd]. (** [import_socket_listening ~sw ~close_unix fd] is an Eio listening socket that uses [fd].
The socket object will be closed when [sw] finishes.
The [close_unix] and [sw] arguments are passed to {!Fd.of_unix}. *)
val import_socket_datagram : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [< `Unix_fd | datagram_socket_ty] r
(** [import_socket_datagram ~sw ~close_unix fd] is an Eio datagram socket that uses [fd].
The socket object will be closed when [sw] finishes. The socket object will be closed when [sw] finishes.
@ -75,7 +82,7 @@ val socketpair_stream :
?domain:Unix.socket_domain -> ?domain:Unix.socket_domain ->
?protocol:int -> ?protocol:int ->
unit -> unit ->
[`Unix_fd | stream_socket_ty] r * [`Unix_fd | stream_socket_ty] r [< `Unix_fd | stream_socket_ty] r * [< `Unix_fd | stream_socket_ty] r
(** [socketpair_stream ~sw ()] returns a connected pair of flows, such that writes to one can be read by the other. (** [socketpair_stream ~sw ()] returns a connected pair of flows, such that writes to one can be read by the other.
This creates OS-level resources using [socketpair(2)]. This creates OS-level resources using [socketpair(2)].
@ -86,7 +93,7 @@ val socketpair_datagram :
?domain:Unix.socket_domain -> ?domain:Unix.socket_domain ->
?protocol:int -> ?protocol:int ->
unit -> unit ->
[`Unix_fd | datagram_socket_ty] r * [`Unix_fd | datagram_socket_ty] r [< `Unix_fd | datagram_socket_ty] r * [< `Unix_fd | datagram_socket_ty] r
(** [socketpair_datagram ~sw ()] returns a connected pair of flows, such that writes to one can be read by the other. (** [socketpair_datagram ~sw ()] returns a connected pair of flows, such that writes to one can be read by the other.
This creates OS-level resources using [socketpair(2)]. This creates OS-level resources using [socketpair(2)].
@ -100,6 +107,8 @@ val getnameinfo : Eio.Net.Sockaddr.t -> (string * string)
type _ Effect.t += type _ Effect.t +=
| Import_socket_stream : | Import_socket_stream :
Switch.t * bool * Unix.file_descr -> [`Unix_fd | stream_socket_ty] r Effect.t (** See {!import_socket_stream} *) Switch.t * bool * Unix.file_descr -> [`Unix_fd | stream_socket_ty] r Effect.t (** See {!import_socket_stream} *)
| Import_socket_listening :
Switch.t * bool * Unix.file_descr -> [`Unix_fd | listening_socket_ty] r Effect.t (** See {!import_socket_listening} *)
| Import_socket_datagram : | Import_socket_datagram :
Switch.t * bool * Unix.file_descr -> [`Unix_fd | datagram_socket_ty] r Effect.t (** See {!import_socket_datagram} *) Switch.t * bool * Unix.file_descr -> [`Unix_fd | datagram_socket_ty] r Effect.t (** See {!import_socket_datagram} *)
| Socketpair_stream : Eio.Switch.t * Unix.socket_domain * int -> | Socketpair_stream : Eio.Switch.t * Unix.socket_domain * int ->

View File

@ -98,7 +98,13 @@ let get t =
None None
let close_fd fd = let close_fd fd =
Eio.Private.Trace.with_span "close" (fun () -> Unix.close fd) Eio.Private.Trace.with_span "close" (fun () ->
try
Unix.close fd
with Unix.Unix_error (ECONNRESET, _, _) ->
(* For FreeBSD. See https://github.com/ocaml-multicore/eio/issues/786 *)
()
)
(* Note: we could simplify this a bit by incrementing [t.ops], as [remove] does. (* Note: we could simplify this a bit by incrementing [t.ops], as [remove] does.
However, that makes dscheck too slow. *) However, that makes dscheck too slow. *)

View File

@ -7,10 +7,10 @@ let prepare_for_await () =
| _ -> () | _ -> ()
and await () = and await () =
if Atomic.get state != `Released then if Atomic.get state != `Released then
Suspend.enter "domain-local-await" @@ fun ctx enqueue -> Eio.Private.Suspend.enter "domain-local-await" @@ fun ctx enqueue ->
let awaiting = `Awaiting enqueue in let awaiting = `Awaiting enqueue in
if Atomic.compare_and_set state `Init awaiting then ( if Atomic.compare_and_set state `Init awaiting then (
Cancel.Fiber_context.set_cancel_fn ctx (fun ex -> Eio.Private.Fiber_context.set_cancel_fn ctx (fun ex ->
if Atomic.compare_and_set state awaiting `Released then ( if Atomic.compare_and_set state awaiting `Released then (
enqueue (Error ex) enqueue (Error ex)
) )

1
lib_eio/utils/dla.mli Normal file
View File

@ -0,0 +1 @@
val prepare_for_await : unit -> Domain_local_await.t

View File

@ -1,4 +1,4 @@
(library (library
(name eio_utils) (name eio_utils)
(public_name eio.utils) (public_name eio.utils)
(libraries eio psq fmt optint)) (libraries eio psq fmt optint domain-local-await))

View File

@ -5,3 +5,4 @@
module Lf_queue = Lf_queue module Lf_queue = Lf_queue
module Suspended = Suspended module Suspended = Suspended
module Zzz = Zzz module Zzz = Zzz
module Dla = Dla

View File

@ -38,82 +38,6 @@ let get_dir_fd_opt (Eio.Resource.T (t, ops)) =
| Some f -> Some (f t) | Some f -> Some (f t)
| None -> None | None -> None
(* When copying between a source with an FD and a sink with an FD, we can share the chunk
and avoid copying. *)
let fast_copy src dst =
let fallback () =
(* No chunks available. Use regular memory instead. *)
let buf = Cstruct.create 4096 in
try
while true do
let got = Low_level.readv src [buf] in
Low_level.writev dst [Cstruct.sub buf 0 got]
done
with End_of_file -> ()
in
Low_level.with_chunk ~fallback @@ fun chunk ->
let chunk_size = Uring.Region.length chunk in
try
while true do
let got = Low_level.read_upto src chunk chunk_size in
Low_level.write dst chunk got
done
with End_of_file -> ()
(* Try a fast copy using splice. If the FDs don't support that, switch to copying. *)
let _fast_copy_try_splice src dst =
try
while true do
let _ : int = Low_level.splice src ~dst ~len:max_int in
()
done
with
| End_of_file -> ()
| Eio.Exn.Io (Eio.Exn.X Eio_unix.Unix_error ((EAGAIN | EINVAL), "splice", _), _) -> fast_copy src dst
(* XXX workaround for issue #319, PR #327 *)
let fast_copy_try_splice src dst = fast_copy src dst
let[@tail_mod_cons] rec list_take n = function
| [] -> []
| x :: xs ->
if n = 0 then []
else x :: list_take (n - 1) xs
let truncate_to_iomax xs =
if List.compare_length_with xs Uring.iov_max <= 0 then xs
else list_take Uring.iov_max xs
(* Copy using the [Read_source_buffer] optimisation.
Avoids a copy if the source already has the data. *)
let copy_with_rsb rsb dst =
let write xs = Low_level.writev_single dst (truncate_to_iomax xs) in
try
while true do rsb write done
with End_of_file -> ()
(* Copy by allocating a chunk from the pre-shared buffer and asking
the source to write into it. This used when the other methods
aren't available. *)
let fallback_copy (type src) (module Src : Eio.Flow.Pi.SOURCE with type t = src) src dst =
let fallback () =
(* No chunks available. Use regular memory instead. *)
let buf = Cstruct.create 4096 in
try
while true do
let got = Src.single_read src buf in
Low_level.writev dst [Cstruct.sub buf 0 got]
done
with End_of_file -> ()
in
Low_level.with_chunk ~fallback @@ fun chunk ->
let chunk_cs = Uring.Region.to_cstruct chunk in
try
while true do
let got = Src.single_read src chunk_cs in
Low_level.write dst chunk got
done
with End_of_file -> ()
module Datagram_socket = struct module Datagram_socket = struct
type tag = [`Generic | `Unix] type tag = [`Generic | `Unix]
@ -145,78 +69,6 @@ let datagram_handler = Eio_unix.Pi.datagram_handler (module Datagram_socket)
let datagram_socket fd = let datagram_socket fd =
Eio.Resource.T (fd, datagram_handler) Eio.Resource.T (fd, datagram_handler)
module Flow = struct
type tag = [`Generic | `Unix]
type t = Eio_unix.Fd.t
let fd t = t
let close = Eio_unix.Fd.close
let is_tty t = Fd.use_exn "isatty" t Unix.isatty
let stat = Low_level.fstat
let single_read t buf =
if is_tty t then (
(* Work-around for https://github.com/axboe/liburing/issues/354
(should be fixed in Linux 5.14) *)
Low_level.await_readable t
);
Low_level.readv t [buf]
let pread t ~file_offset bufs =
Low_level.readv ~file_offset t bufs
let pwrite t ~file_offset bufs =
Low_level.writev_single ~file_offset t (truncate_to_iomax bufs)
let read_methods = []
let single_write t bufs = Low_level.writev_single t (truncate_to_iomax bufs)
let copy t ~src =
match Eio_unix.Resource.fd_opt src with
| Some src -> fast_copy_try_splice src t
| None ->
let Eio.Resource.T (src, ops) = src in
let module Src = (val (Eio.Resource.get ops Eio.Flow.Pi.Source)) in
let rec aux = function
| Eio.Flow.Read_source_buffer rsb :: _ -> copy_with_rsb (rsb src) t
| _ :: xs -> aux xs
| [] -> fallback_copy (module Src) src t
in
aux Src.read_methods
let shutdown t cmd =
Low_level.shutdown t @@ match cmd with
| `Receive -> Unix.SHUTDOWN_RECEIVE
| `Send -> Unix.SHUTDOWN_SEND
| `All -> Unix.SHUTDOWN_ALL
let send_msg t ~fds data =
Low_level.send_msg t ~fds data
let recv_msg_with_fds t ~sw ~max_fds data =
let _addr, n, fds = Low_level.recv_msg_with_fds t ~sw ~max_fds data in
n, fds
let seek = Low_level.lseek
let sync = Low_level.fsync
let truncate = Low_level.ftruncate
end
let flow_handler = Eio_unix.Pi.flow_handler (module Flow)
let flow fd =
let r = Eio.Resource.T (fd, flow_handler) in
(r : [`Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r :>
[< `Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r)
let source fd = (flow fd :> _ Eio_unix.source)
let sink fd = (flow fd :> _ Eio_unix.sink)
module Listening_socket = struct module Listening_socket = struct
type t = Fd.t type t = Fd.t
@ -233,7 +85,7 @@ module Listening_socket = struct
| Unix.ADDR_UNIX path -> `Unix path | Unix.ADDR_UNIX path -> `Unix path
| Unix.ADDR_INET (host, port) -> `Tcp (Eio_unix.Net.Ipaddr.of_unix host, port) | Unix.ADDR_INET (host, port) -> `Tcp (Eio_unix.Net.Ipaddr.of_unix host, port)
in in
let flow = (flow client :> _ Eio.Net.stream_socket) in let flow = (Flow.of_fd client :> _ Eio.Net.stream_socket) in
flow, client_addr flow, client_addr
let listening_addr fd = let listening_addr fd =
@ -261,7 +113,7 @@ let connect ~sw connect_addr =
let sock_unix = Unix.socket ~cloexec:true (socket_domain_of connect_addr) Unix.SOCK_STREAM 0 in let sock_unix = Unix.socket ~cloexec:true (socket_domain_of connect_addr) Unix.SOCK_STREAM 0 in
let sock = Fd.of_unix ~sw ~seekable:false ~close_unix:true sock_unix in let sock = Fd.of_unix ~sw ~seekable:false ~close_unix:true sock_unix in
Low_level.connect sock addr; Low_level.connect sock addr;
(flow sock :> _ Eio_unix.Net.stream_socket) (Flow.of_fd sock :> _ Eio_unix.Net.stream_socket)
module Impl = struct module Impl = struct
type t = unit type t = unit
@ -495,7 +347,7 @@ end = struct
~flags:Uring.Open_flags.cloexec ~flags:Uring.Open_flags.cloexec
~perm:0 ~perm:0
in in
(flow fd :> Eio.File.ro_ty r) (Flow.of_fd fd :> Eio.File.ro_ty r)
let open_out t ~sw ~append ~create path = let open_out t ~sw ~append ~create path =
let perm, flags = let perm, flags =
@ -511,7 +363,7 @@ end = struct
~flags:Uring.Open_flags.(cloexec + flags) ~flags:Uring.Open_flags.(cloexec + flags)
~perm ~perm
in in
(flow fd :> Eio.File.rw_ty r) (Flow.of_fd fd :> Eio.File.rw_ty r)
let native_internal t path = let native_internal t path =
if Filename.is_relative path then ( if Filename.is_relative path then (
@ -592,7 +444,7 @@ end = struct
~flags:Uring.Open_flags.(cloexec + path + (if follow then empty else nofollow)) ~flags:Uring.Open_flags.(cloexec + path + (if follow then empty else nofollow))
~perm:0 ~perm:0
in in
Flow.stat fd Low_level.fstat fd
) )
let rename t old_path t2 new_path = let rename t old_path t2 new_path =
@ -600,6 +452,9 @@ end = struct
| Some fd2 -> Low_level.rename t.fd old_path fd2 new_path | Some fd2 -> Low_level.rename t.fd old_path fd2 new_path
| None -> raise (Unix.Unix_error (Unix.EXDEV, "rename-dst", new_path)) | None -> raise (Unix.Unix_error (Unix.EXDEV, "rename-dst", new_path))
let symlink ~link_to t path =
Low_level.symlink ~link_to t.fd path
let pp f t = Fmt.string f (String.escaped t.label) let pp f t = Fmt.string f (String.escaped t.label)
let fd t = t.fd let fd t = t.fd
@ -619,26 +474,13 @@ end
let dir ~label ~path fd = Eio.Resource.T (Dir.v ~label ~path fd, Dir_handler.v) let dir ~label ~path fd = Eio.Resource.T (Dir.v ~label ~path fd, Dir_handler.v)
module Secure_random = struct
type t = unit
let single_read () buf = Low_level.getrandom buf; Cstruct.length buf
let read_methods = []
end
let secure_random =
let ops = Eio.Flow.Pi.source (module Secure_random) in
Eio.Resource.T ((), ops)
let stdenv ~run_event_loop = let stdenv ~run_event_loop =
let stdin = source Eio_unix.Fd.stdin in
let stdout = sink Eio_unix.Fd.stdout in
let stderr = sink Eio_unix.Fd.stderr in
let fs = (dir ~label:"fs" ~path:"" Fs, "") in let fs = (dir ~label:"fs" ~path:"" Fs, "") in
let cwd = (dir ~label:"cwd" ~path:"" Cwd, "") in let cwd = (dir ~label:"cwd" ~path:"" Cwd, "") in
object (_ : stdenv) object (_ : stdenv)
method stdin = stdin method stdin = Flow.stdin
method stdout = stdout method stdout = Flow.stdout
method stderr = stderr method stderr = Flow.stderr
method net = net method net = net
method process_mgr = process_mgr method process_mgr = process_mgr
method domain_mgr = domain_mgr ~run_event_loop method domain_mgr = domain_mgr ~run_event_loop
@ -646,7 +488,7 @@ let stdenv ~run_event_loop =
method mono_clock = mono_clock method mono_clock = mono_clock
method fs = (fs :> Eio.Fs.dir_ty Eio.Path.t) method fs = (fs :> Eio.Fs.dir_ty Eio.Path.t)
method cwd = (cwd :> Eio.Fs.dir_ty Eio.Path.t) method cwd = (cwd :> Eio.Fs.dir_ty Eio.Path.t)
method secure_random = secure_random method secure_random = Flow.secure_random
method debug = Eio.Private.Debug.v method debug = Eio.Private.Debug.v
method backend_id = "linux" method backend_id = "linux"
end end
@ -660,7 +502,11 @@ let run_event_loop (type a) ?fallback config (main : _ -> a) arg : a =
| Eio_unix.Private.Get_monotonic_clock -> Some (fun k -> continue k mono_clock) | Eio_unix.Private.Get_monotonic_clock -> Some (fun k -> continue k mono_clock)
| Eio_unix.Net.Import_socket_stream (sw, close_unix, fd) -> Some (fun k -> | Eio_unix.Net.Import_socket_stream (sw, close_unix, fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in
continue k (flow fd :> _ Eio_unix.Net.stream_socket) continue k (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket)
)
| Eio_unix.Net.Import_socket_listening (sw, close_unix, fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in
continue k (listening_socket fd)
) )
| Eio_unix.Net.Import_socket_datagram (sw, close_unix, fd) -> Some (fun k -> | Eio_unix.Net.Import_socket_datagram (sw, close_unix, fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in
@ -669,8 +515,8 @@ let run_event_loop (type a) ?fallback config (main : _ -> a) arg : a =
| Eio_unix.Net.Socketpair_stream (sw, domain, protocol) -> Some (fun k -> | Eio_unix.Net.Socketpair_stream (sw, domain, protocol) -> Some (fun k ->
match match
let a, b = Unix.socketpair ~cloexec:true domain Unix.SOCK_STREAM protocol in let a, b = Unix.socketpair ~cloexec:true domain Unix.SOCK_STREAM protocol in
let a = Fd.of_unix ~sw ~seekable:false ~close_unix:true a |> flow in let a = Fd.of_unix ~sw ~seekable:false ~close_unix:true a |> Flow.of_fd in
let b = Fd.of_unix ~sw ~seekable:false ~close_unix:true b |> flow in let b = Fd.of_unix ~sw ~seekable:false ~close_unix:true b |> Flow.of_fd in
((a :> _ Eio_unix.Net.stream_socket), (b :> _ Eio_unix.Net.stream_socket)) ((a :> _ Eio_unix.Net.stream_socket), (b :> _ Eio_unix.Net.stream_socket))
with with
| r -> continue k r | r -> continue k r
@ -691,8 +537,8 @@ let run_event_loop (type a) ?fallback config (main : _ -> a) arg : a =
| Eio_unix.Private.Pipe sw -> Some (fun k -> | Eio_unix.Private.Pipe sw -> Some (fun k ->
match match
let r, w = Low_level.pipe ~sw in let r, w = Low_level.pipe ~sw in
let r = (flow r :> _ Eio_unix.source) in let r = (Flow.of_fd r :> _ Eio_unix.source) in
let w = (flow w :> _ Eio_unix.sink) in let w = (Flow.of_fd w :> _ Eio_unix.sink) in
(r, w) (r, w)
with with
| r -> continue k r | r -> continue k r

View File

@ -39,7 +39,11 @@
#ifndef SYS_clone3 #ifndef SYS_clone3
# define SYS_clone3 435 # define SYS_clone3 435
# define CLONE_PIDFD 0x00001000 # define CLONE_PIDFD 0x00001000
struct clone_args { #endif
// struct clone_args isn't defined in linux-lts headers, so define it here
// Note that this struct is versioned by size. See linux/sched.h for details
struct caml_eio_clone_args {
uint64_t flags; uint64_t flags;
uint64_t pidfd; uint64_t pidfd;
uint64_t child_tid; uint64_t child_tid;
@ -48,11 +52,7 @@ struct clone_args {
uint64_t stack; uint64_t stack;
uint64_t stack_size; uint64_t stack_size;
uint64_t tls; uint64_t tls;
uint64_t set_tid;
uint64_t set_tid_size;
uint64_t cgroup;
}; };
#endif
// Make sure we have enough space for at least one entry. // Make sure we have enough space for at least one entry.
#define DIRENT_BUF_SIZE (PATH_MAX + sizeof(struct dirent64)) #define DIRENT_BUF_SIZE (PATH_MAX + sizeof(struct dirent64))
@ -97,6 +97,24 @@ CAMLprim value caml_eio_renameat(value v_old_fd, value v_old_path, value v_new_f
CAMLreturn(Val_unit); CAMLreturn(Val_unit);
} }
CAMLprim value caml_eio_symlinkat(value v_old_path, value v_new_fd, value v_new_path) {
CAMLparam2(v_old_path, v_new_path);
char *old_path;
char *new_path;
int ret;
caml_unix_check_path(v_old_path, "symlinkat-old");
caml_unix_check_path(v_new_path, "symlinkat-new");
old_path = caml_stat_strdup(String_val(v_old_path));
new_path = caml_stat_strdup(String_val(v_new_path));
caml_enter_blocking_section();
ret = symlinkat(old_path, Int_val(v_new_fd), new_path);
caml_leave_blocking_section();
caml_stat_free(old_path);
caml_stat_free(new_path);
if (ret == -1) uerror("symlinkat", v_old_path);
CAMLreturn(Val_unit);
}
CAMLprim value caml_eio_getrandom(value v_ba, value v_off, value v_len) { CAMLprim value caml_eio_getrandom(value v_ba, value v_off, value v_len) {
CAMLparam1(v_ba); CAMLparam1(v_ba);
ssize_t ret; ssize_t ret;
@ -160,9 +178,9 @@ static int pidfd_open(pid_t pid, unsigned int flags) {
/* Like clone3, but falls back to fork if not supported. /* Like clone3, but falls back to fork if not supported.
Also, raises exceptions rather then returning an error. */ Also, raises exceptions rather then returning an error. */
static pid_t clone3_with_fallback(struct clone_args *cl_args) { static pid_t clone3_with_fallback(struct caml_eio_clone_args *cl_args) {
int *pidfd = (int *)(uintptr_t) cl_args->pidfd; int *pidfd = (int *)(uintptr_t) cl_args->pidfd;
pid_t child_pid = syscall(SYS_clone3, cl_args, sizeof(struct clone_args)); pid_t child_pid = syscall(SYS_clone3, cl_args, sizeof(struct caml_eio_clone_args));
if (child_pid >= 0) if (child_pid >= 0)
return child_pid; /* Success! */ return child_pid; /* Success! */
@ -198,7 +216,7 @@ CAMLprim value caml_eio_clone3(value v_errors, value v_actions) {
CAMLlocal1(v_result); CAMLlocal1(v_result);
pid_t child_pid; pid_t child_pid;
int pidfd = -1; /* Is automatically close-on-exec */ int pidfd = -1; /* Is automatically close-on-exec */
struct clone_args cl_args = { struct caml_eio_clone_args cl_args = {
.flags = CLONE_PIDFD, .flags = CLONE_PIDFD,
.pidfd = (uintptr_t) &pidfd, .pidfd = (uintptr_t) &pidfd,
.exit_signal = SIGCHLD, /* Needed for wait4 to work if we exit before exec */ .exit_signal = SIGCHLD, /* Needed for wait4 to work if we exit before exec */

157
lib_eio_linux/flow.ml Normal file
View File

@ -0,0 +1,157 @@
open Eio.Std
(* When copying between a source with an FD and a sink with an FD, we can share the chunk
and avoid copying. *)
let fast_copy src dst =
let fallback () =
(* No chunks available. Use regular memory instead. *)
let buf = Cstruct.create 4096 in
try
while true do
let got = Low_level.readv src [buf] in
Low_level.writev dst [Cstruct.sub buf 0 got]
done
with End_of_file -> ()
in
Low_level.with_chunk ~fallback @@ fun chunk ->
let chunk_size = Uring.Region.length chunk in
try
while true do
let got = Low_level.read_upto src chunk chunk_size in
Low_level.write dst chunk got
done
with End_of_file -> ()
(* Try a fast copy using splice. If the FDs don't support that, switch to copying. *)
let _fast_copy_try_splice src dst =
try
while true do
let _ : int = Low_level.splice src ~dst ~len:max_int in
()
done
with
| End_of_file -> ()
| Eio.Exn.Io (Eio.Exn.X Eio_unix.Unix_error ((EAGAIN | EINVAL), "splice", _), _) -> fast_copy src dst
(* XXX workaround for issue #319, PR #327 *)
let fast_copy_try_splice src dst = fast_copy src dst
let[@tail_mod_cons] rec list_take n = function
| [] -> []
| x :: xs ->
if n = 0 then []
else x :: list_take (n - 1) xs
let truncate_to_iomax xs =
if List.compare_length_with xs Uring.iov_max <= 0 then xs
else list_take Uring.iov_max xs
(* Copy using the [Read_source_buffer] optimisation.
Avoids a copy if the source already has the data. *)
let copy_with_rsb rsb dst =
let write xs = Low_level.writev_single dst (truncate_to_iomax xs) in
try
while true do rsb write done
with End_of_file -> ()
(* Copy by allocating a chunk from the pre-shared buffer and asking
the source to write into it. This used when the other methods
aren't available. *)
let fallback_copy (type src) (module Src : Eio.Flow.Pi.SOURCE with type t = src) src dst =
let fallback () =
(* No chunks available. Use regular memory instead. *)
let buf = Cstruct.create 4096 in
try
while true do
let got = Src.single_read src buf in
Low_level.writev dst [Cstruct.sub buf 0 got]
done
with End_of_file -> ()
in
Low_level.with_chunk ~fallback @@ fun chunk ->
let chunk_cs = Uring.Region.to_cstruct chunk in
try
while true do
let got = Src.single_read src chunk_cs in
Low_level.write dst chunk got
done
with End_of_file -> ()
module Impl = struct
type tag = [`Generic | `Unix]
type t = Eio_unix.Fd.t
let fd t = t
let close = Eio_unix.Fd.close
let stat = Low_level.fstat
let single_read t buf =
Low_level.readv t [buf]
let pread t ~file_offset bufs =
Low_level.readv ~file_offset t bufs
let pwrite t ~file_offset bufs =
Low_level.writev_single ~file_offset t (truncate_to_iomax bufs)
let read_methods = []
let single_write t bufs = Low_level.writev_single t (truncate_to_iomax bufs)
let copy t ~src =
match Eio_unix.Resource.fd_opt src with
| Some src -> fast_copy_try_splice src t
| None ->
let Eio.Resource.T (src, ops) = src in
let module Src = (val (Eio.Resource.get ops Eio.Flow.Pi.Source)) in
let rec aux = function
| Eio.Flow.Read_source_buffer rsb :: _ -> copy_with_rsb (rsb src) t
| _ :: xs -> aux xs
| [] -> fallback_copy (module Src) src t
in
aux Src.read_methods
let shutdown t cmd =
Low_level.shutdown t @@ match cmd with
| `Receive -> Unix.SHUTDOWN_RECEIVE
| `Send -> Unix.SHUTDOWN_SEND
| `All -> Unix.SHUTDOWN_ALL
let send_msg t ~fds data =
Low_level.send_msg t ~fds data
let recv_msg_with_fds t ~sw ~max_fds data =
let _addr, n, fds = Low_level.recv_msg_with_fds t ~sw ~max_fds data in
n, fds
let seek = Low_level.lseek
let sync = Low_level.fsync
let truncate = Low_level.ftruncate
end
let flow_handler = Eio_unix.Pi.flow_handler (module Impl)
let of_fd fd =
let r = Eio.Resource.T (fd, flow_handler) in
(r : [`Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r :>
[< `Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r)
let source fd = (of_fd fd :> Eio_unix.source_ty r)
let sink fd = (of_fd fd :> Eio_unix.sink_ty r)
let stdin = source Eio_unix.Fd.stdin
let stdout = sink Eio_unix.Fd.stdout
let stderr = sink Eio_unix.Fd.stderr
module Secure_random = struct
type t = unit
let single_read () buf = Low_level.getrandom buf; Cstruct.length buf
let read_methods = []
end
let secure_random =
let ops = Eio.Flow.Pi.source (module Secure_random) in
Eio.Resource.T ((), ops)

9
lib_eio_linux/flow.mli Normal file
View File

@ -0,0 +1,9 @@
open Eio.Std
val of_fd : Eio_unix.Fd.t -> [< `Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r
val stdin : Eio_unix.source_ty r
val stdout : Eio_unix.sink_ty r
val stderr : Eio_unix.sink_ty r
val secure_random : Eio.Flow.source_ty r

View File

@ -207,11 +207,39 @@ let write ?file_offset:off fd buf len =
raise @@ Err.wrap (Uring.error_of_errno res) "write" "" raise @@ Err.wrap (Uring.error_of_errno res) "write" ""
) )
let alloc_fixed () = Effect.perform Sched.Alloc let alloc_fixed () =
let s = Sched.get () in
match s.mem with
| None -> None
| Some mem ->
match Uring.Region.alloc mem with
| buf -> Some buf
| exception Uring.Region.No_space -> None
let alloc_fixed_or_wait () = Effect.perform Sched.Alloc_or_wait let alloc_fixed_or_wait () =
let s = Sched.get () in
match s.mem with
| None -> failwith "No fixed buffer available"
| Some mem ->
match Uring.Region.alloc mem with
| buf -> buf
| exception Uring.Region.No_space ->
let id = Eio.Private.Trace.mint_id () in
let trigger = Eio.Private.Single_waiter.create () in
let node = Lwt_dllist.add_r trigger s.mem_q in
try
Eio.Private.Single_waiter.await trigger "alloc_fixed_or_wait" id
with ex ->
Lwt_dllist.remove node;
raise ex
let free_fixed buf = Effect.perform (Sched.Free buf) let rec free_fixed buf =
let s = Sched.get () in
match Lwt_dllist.take_opt_l s.mem_q with
| None -> Uring.Region.free buf
| Some k ->
if not (Eio.Private.Single_waiter.wake k (Ok buf)) then
free_fixed buf (* [k] was already cancelled, but not yet removed from the queue *)
let splice src ~dst ~len = let splice src ~dst ~len =
Fd.use_exn "splice-src" src @@ fun src -> Fd.use_exn "splice-src" src @@ fun src ->
@ -330,6 +358,8 @@ external eio_mkdirat : Unix.file_descr -> string -> Unix.file_perm -> unit = "ca
external eio_renameat : Unix.file_descr -> string -> Unix.file_descr -> string -> unit = "caml_eio_renameat" external eio_renameat : Unix.file_descr -> string -> Unix.file_descr -> string -> unit = "caml_eio_renameat"
external eio_symlinkat : string -> Unix.file_descr -> string -> unit = "caml_eio_symlinkat"
external eio_getrandom : Cstruct.buffer -> int -> int -> int = "caml_eio_getrandom" external eio_getrandom : Cstruct.buffer -> int -> int -> int = "caml_eio_getrandom"
external eio_getdents : Unix.file_descr -> string list = "caml_eio_getdents" external eio_getdents : Unix.file_descr -> string list = "caml_eio_getdents"
@ -450,6 +480,12 @@ let rename old_dir old_path new_dir new_path =
new_parent new_leaf new_parent new_leaf
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap_fs code name arg with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap_fs code name arg
let symlink ~link_to dir path =
with_parent_dir "symlinkat-new" dir path @@ fun parent leaf ->
try
eio_symlinkat link_to parent leaf
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap_fs code name arg
let shutdown socket command = let shutdown socket command =
try try
Fd.use_exn "shutdown" socket @@ fun fd -> Fd.use_exn "shutdown" socket @@ fun fd ->
@ -563,7 +599,10 @@ module Process = struct
let exit_status, set_exit_status = Promise.create () in let exit_status, set_exit_status = Promise.create () in
let t = let t =
Fd.use_exn "errors-w" errors_w @@ fun errors_w -> Fd.use_exn "errors-w" errors_w @@ fun errors_w ->
let pid, pid_fd = eio_spawn errors_w c_actions in let pid, pid_fd =
Eio.Private.Trace.with_span "spawn" @@ fun () ->
eio_spawn errors_w c_actions
in
let pid_fd = Fd.of_unix ~sw ~seekable:false ~close_unix:true pid_fd in let pid_fd = Fd.of_unix ~sw ~seekable:false ~close_unix:true pid_fd in
{ pid; pid_fd; exit_status } { pid; pid_fd; exit_status }
in in

View File

@ -150,6 +150,9 @@ val unlink : rmdir:bool -> dir_fd -> string -> unit
val rename : dir_fd -> string -> dir_fd -> string -> unit val rename : dir_fd -> string -> dir_fd -> string -> unit
(** [rename old_dir old_path new_dir new_path] renames [old_dir / old_path] as [new_dir / new_path]. *) (** [rename old_dir old_path new_dir new_path] renames [old_dir / old_path] as [new_dir / new_path]. *)
val symlink : link_to:string -> dir_fd -> string -> unit
(** [symlink ~link_to dir path] creates a new symlink at [dir / path] pointing to [link_to]. *)
val pipe : sw:Switch.t -> fd * fd val pipe : sw:Switch.t -> fd * fd
(** [pipe ~sw] returns a pair [r, w] with the readable and writeable ends of a new pipe. *) (** [pipe ~sw] returns a pair [r, w] with the readable and writeable ends of a new pipe. *)

View File

@ -5,6 +5,7 @@
CAMLprim value caml_eio_eventfd(value); CAMLprim value caml_eio_eventfd(value);
CAMLprim value caml_eio_mkdirat(value, value, value); CAMLprim value caml_eio_mkdirat(value, value, value);
CAMLprim value caml_eio_renameat(value, value, value, value); CAMLprim value caml_eio_renameat(value, value, value, value);
CAMLprim value caml_eio_symlinkat(value, value, value);
CAMLprim value caml_eio_getrandom(value, value, value); CAMLprim value caml_eio_getrandom(value, value, value);
CAMLprim value caml_eio_getdents(value); CAMLprim value caml_eio_getdents(value);
CAMLprim value caml_eio_clone3(value, value); CAMLprim value caml_eio_clone3(value, value);

View File

@ -50,7 +50,7 @@ type t = {
uring: io_job Uring.t; uring: io_job Uring.t;
mem: Uring.Region.t option; mem: Uring.Region.t option;
io_q: (t -> unit) Queue.t; (* waiting for room on [uring] *) io_q: (t -> unit) Queue.t; (* waiting for room on [uring] *)
mem_q : Uring.Region.chunk Suspended.t Queue.t; mem_q : Uring.Region.chunk Eio.Private.Single_waiter.t Lwt_dllist.t;
(* The queue of runnable fibers ready to be resumed. Note: other domains can also add work items here. *) (* The queue of runnable fibers ready to be resumed. Note: other domains can also add work items here. *)
run_q : runnable Lf_queue.t; run_q : runnable Lf_queue.t;
@ -74,9 +74,9 @@ type t = {
type _ Effect.t += type _ Effect.t +=
| Enter : (t -> 'a Suspended.t -> unit) -> 'a Effect.t | Enter : (t -> 'a Suspended.t -> unit) -> 'a Effect.t
| Cancel : io_job Uring.job -> unit Effect.t | Cancel : io_job Uring.job -> unit Effect.t
| Alloc : Uring.Region.chunk option Effect.t | Get : t Effect.t
| Alloc_or_wait : Uring.Region.chunk Effect.t
| Free : Uring.Region.chunk -> unit Effect.t let get () = Effect.perform Get
let wake_buffer = let wake_buffer =
let b = Bytes.create 8 in let b = Bytes.create 8 in
@ -113,7 +113,10 @@ let enter op fn =
Effect.perform (Enter fn) Effect.perform (Enter fn)
let submit uring = let submit uring =
if Uring.sqe_ready uring > 0 then
Trace.with_span "submit" (fun () -> Uring.submit uring) Trace.with_span "submit" (fun () -> Uring.submit uring)
else
0
let rec enqueue_job t fn = let rec enqueue_job t fn =
match fn () with match fn () with
@ -228,7 +231,6 @@ let rec schedule ({run_q; sleep_q; mem_q; uring; _} as st) : [`Exit_scheduler] =
Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *) Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *)
handle_complete st ~runnable result handle_complete st ~runnable result
| None -> | None ->
ignore (submit uring : int);
let timeout = let timeout =
match next_due with match next_due with
| `Wait_until time -> | `Wait_until time ->
@ -239,12 +241,13 @@ let rec schedule ({run_q; sleep_q; mem_q; uring; _} as st) : [`Exit_scheduler] =
| `Nothing -> None | `Nothing -> None
in in
if not (Lf_queue.is_empty st.run_q) then ( if not (Lf_queue.is_empty st.run_q) then (
ignore (submit uring : int);
Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *) Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *)
schedule st schedule st
) else if timeout = None && Uring.active_ops uring = 0 then ( ) else if timeout = None && Uring.active_ops uring = 0 then (
(* Nothing further can happen at this point. (* Nothing further can happen at this point.
If there are no events in progress but also still no memory available, something has gone wrong! *) If there are no events in progress but also still no memory available, something has gone wrong! *)
assert (Queue.length mem_q = 0); assert (Lwt_dllist.length mem_q = 0);
Lf_queue.close st.run_q; (* Just to catch bugs if something tries to enqueue later *) Lf_queue.close st.run_q; (* Just to catch bugs if something tries to enqueue later *)
`Exit_scheduler `Exit_scheduler
) else ( ) else (
@ -254,7 +257,14 @@ let rec schedule ({run_q; sleep_q; mem_q; uring; _} as st) : [`Exit_scheduler] =
If [need_wakeup] is still [true], this is fine because we don't promise to do that. If [need_wakeup] is still [true], this is fine because we don't promise to do that.
If [need_wakeup = false], a wake-up event will arrive and wake us up soon. *) If [need_wakeup = false], a wake-up event will arrive and wake us up soon. *)
Trace.suspend_domain Begin; Trace.suspend_domain Begin;
let result = Uring.wait ?timeout uring in let result =
(* Hack: liburing automatically retries [io_uring_enter] if an
interrupt is received and no timeout is set. However, we need
to return to OCaml mode so any pending signal handlers can
run. See: https://github.com/ocaml-multicore/eio/issues/732 *)
let timeout = Option.value timeout ~default:1e9 in
Uring.wait ~timeout uring
in
Trace.suspend_domain End; Trace.suspend_domain End;
Atomic.set st.need_wakeup false; Atomic.set st.need_wakeup false;
Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *) Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *)
@ -267,6 +277,7 @@ let rec schedule ({run_q; sleep_q; mem_q; uring; _} as st) : [`Exit_scheduler] =
) else ( ) else (
(* Someone added a new job while we were setting [need_wakeup] to [true]. (* Someone added a new job while we were setting [need_wakeup] to [true].
They might or might not have seen that, so we can't be sure they'll send an event. *) They might or might not have seen that, so we can't be sure they'll send an event. *)
ignore (submit uring : int);
Atomic.set st.need_wakeup false; Atomic.set st.need_wakeup false;
Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *) Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *)
schedule st schedule st
@ -328,21 +339,6 @@ and complete_rw_req st ({len; cur_off; action; _} as req) res =
| _, Exactly len -> Suspended.continue action len | _, Exactly len -> Suspended.continue action len
| n, Upto _ -> Suspended.continue action n | n, Upto _ -> Suspended.continue action n
let alloc_buf_or_wait st k =
match st.mem with
| None -> Suspended.discontinue k (Failure "No fixed buffer available")
| Some mem ->
match Uring.Region.alloc mem with
| buf -> Suspended.continue k buf
| exception Uring.Region.No_space ->
Queue.push k st.mem_q;
schedule st
let free_buf st buf =
match Queue.take_opt st.mem_q with
| None -> Uring.Region.free buf
| Some k -> enqueue_thread st k buf
let rec enqueue_poll_add fd poll_mask st action = let rec enqueue_poll_add fd poll_mask st action =
Trace.log "poll_add"; Trace.log "poll_add";
let retry = with_cancel_hook ~action st (fun () -> let retry = with_cancel_hook ~action st (fun () ->
@ -400,8 +396,9 @@ let run ~extra_effects st main arg =
Fiber_context.destroy fiber; Fiber_context.destroy fiber;
Printexc.raise_with_backtrace ex (Printexc.get_raw_backtrace ()) Printexc.raise_with_backtrace ex (Printexc.get_raw_backtrace ())
); );
effc = fun (type a) (e : a Effect.t) -> effc = fun (type a) (e : a Effect.t) : ((a, _) continuation -> _) option ->
match e with match e with
| Get -> Some (fun k -> continue k st)
| Enter fn -> Some (fun k -> | Enter fn -> Some (fun k ->
match Fiber_context.get_error fiber with match Fiber_context.get_error fiber with
| Some e -> discontinue k e | Some e -> discontinue k e
@ -456,22 +453,6 @@ let run ~extra_effects st main arg =
Eio_unix.Private.Thread_pool.submit st.thread_pool ~ctx:fiber ~enqueue fn; Eio_unix.Private.Thread_pool.submit st.thread_pool ~ctx:fiber ~enqueue fn;
schedule st schedule st
) )
| Alloc -> Some (fun k ->
match st.mem with
| None -> continue k None
| Some mem ->
match Uring.Region.alloc mem with
| buf -> continue k (Some buf)
| exception Uring.Region.No_space -> continue k None
)
| Alloc_or_wait -> Some (fun k ->
let k = { Suspended.k; fiber } in
alloc_buf_or_wait st k
)
| Free buf -> Some (fun k ->
free_buf st buf;
continue k ()
)
| e -> extra_effects.effc e | e -> extra_effects.effc e
} }
in in
@ -479,7 +460,7 @@ let run ~extra_effects st main arg =
let `Exit_scheduler = let `Exit_scheduler =
let new_fiber = Fiber_context.make_root () in let new_fiber = Fiber_context.make_root () in
Domain_local_await.using Domain_local_await.using
~prepare_for_await:Eio.Private.Dla.prepare_for_await ~prepare_for_await:Eio_utils.Dla.prepare_for_await
~while_running:(fun () -> ~while_running:(fun () ->
fork ~new_fiber (fun () -> fork ~new_fiber (fun () ->
Switch.run_protected ~name:"eio_linux" (fun sw -> Switch.run_protected ~name:"eio_linux" (fun sw ->
@ -536,11 +517,14 @@ let with_sched ?(fallback=no_fallback) config fn =
| exception Unix.Unix_error(EPERM, _, _) -> fallback (`Msg "io_uring is not available (permission denied)") | exception Unix.Unix_error(EPERM, _, _) -> fallback (`Msg "io_uring is not available (permission denied)")
| uring -> | uring ->
let probe = Uring.get_probe uring in let probe = Uring.get_probe uring in
if not (Uring.op_supported probe Uring.Op.shutdown) then ( if not (Uring.op_supported probe Uring.Op.mkdirat) then (
Uring.exit uring; Uring.exit uring;
fallback (`Msg "Linux >= 5.11 is required for io_uring support") fallback (`Msg "Linux >= 5.15 is required for io_uring support")
) else ( ) else (
statx_works := Uring.op_supported probe Uring.Op.msg_ring; (* The reason for an if here is to make sure we only set it once, when
the first domain is starting. This is just to avoid a tsan warning. *)
if not !statx_works && Uring.op_supported probe Uring.Op.msg_ring then
statx_works := true;
match match
let mem = let mem =
let fixed_buf_len = block_size * n_blocks in let fixed_buf_len = block_size * n_blocks in
@ -555,7 +539,7 @@ let with_sched ?(fallback=no_fallback) config fn =
Lf_queue.push run_q IO; Lf_queue.push run_q IO;
let sleep_q = Zzz.create () in let sleep_q = Zzz.create () in
let io_q = Queue.create () in let io_q = Queue.create () in
let mem_q = Queue.create () in let mem_q = Lwt_dllist.create () in
with_eventfd @@ fun eventfd -> with_eventfd @@ fun eventfd ->
let thread_pool = Eio_unix.Private.Thread_pool.create ~sleep_q in let thread_pool = Eio_unix.Private.Thread_pool.create ~sleep_q in
fn { mem; uring; run_q; io_q; mem_q; eventfd; need_wakeup = Atomic.make false; sleep_q; thread_pool } fn { mem; uring; run_q; io_q; mem_q; eventfd; need_wakeup = Atomic.make false; sleep_q; thread_pool }

View File

@ -130,7 +130,7 @@ let test_read_exact () =
let ( / ) = Eio.Path.( / ) in let ( / ) = Eio.Path.( / ) in
let path = env#cwd / "test.data" in let path = env#cwd / "test.data" in
let msg = "hello" in let msg = "hello" in
Eio.Path.save path ("!" ^ msg) ~create:(`Exclusive 0o600); Eio.Path.save path ("!" ^ msg) ~create:(`Or_truncate 0o600);
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
let fd = Eio_linux.Low_level.openat2 ~sw let fd = Eio_linux.Low_level.openat2 ~sw
~access:`R ~access:`R
@ -162,7 +162,7 @@ let test_statx () =
Eio_linux.run ~queue_depth:4 @@ fun env -> Eio_linux.run ~queue_depth:4 @@ fun env ->
let ( / ) = Eio.Path.( / ) in let ( / ) = Eio.Path.( / ) in
let path = env#cwd / "test2.data" in let path = env#cwd / "test2.data" in
Eio.Path.with_open_out path ~create:(`Exclusive 0o600) @@ fun file -> Eio.Path.with_open_out path ~create:(`Or_truncate 0o600) @@ fun file ->
Eio.Flow.copy_string "hello" file; Eio.Flow.copy_string "hello" file;
let buf = Uring.Statx.create () in let buf = Uring.Statx.create () in
let test expected_len ~follow dir path = let test expected_len ~follow dir path =
@ -198,6 +198,51 @@ let test_statx () =
); );
() ()
(* Ensure that an OCaml signal handler will run, even if we're sleeping in liburing at the time.
The problem here is that [__sys_io_uring_enter2] doesn't return EINTR, because it did successfully
submit an item. This causes liburing to retry without giving our OCaml signal handler a chance to run.
Note: we can't run this test with a timeout because liburing does return in that case! *)
let test_signal_race () =
Eio_linux.run @@ fun _env ->
let cond = Eio.Condition.create () in
let handle _ = Eio.Condition.broadcast cond in
Sys.(set_signal sigalrm) (Signal_handle handle);
Fiber.both
(fun () -> Eio.Condition.await_no_mutex cond)
(fun () -> ignore (Unix.setitimer ITIMER_REAL { it_interval = 0.; it_value = 0.001 } : Unix.interval_timer_status))
let test_alloc_fixed_or_wait () =
Eio_linux.run ~n_blocks:1 @@ fun _env ->
let block = Eio_linux.Low_level.alloc_fixed_or_wait () in
(* We have to wait for the block, but get cancelled while waiting. *)
begin
try
Fiber.both
(fun () -> ignore (Eio_linux.Low_level.alloc_fixed_or_wait () : Uring.Region.chunk))
(fun () -> raise Exit);
with Exit -> ()
end;
(* We have to wait for the block, and get it when the old one is freed. *)
Fiber.both
(fun () ->
let x = Eio_linux.Low_level.alloc_fixed_or_wait () in
Eio_linux.Low_level.free_fixed x
)
(fun () ->
Eio_linux.Low_level.free_fixed block
);
(* The old block is passed to the waiting fiber, but it's cancelled. *)
let block = Eio_linux.Low_level.alloc_fixed_or_wait () in
Fiber.both
(fun () ->
Fiber.first
(fun () -> ignore (Eio_linux.Low_level.alloc_fixed_or_wait ()); assert false)
(fun () -> ())
)
(fun () -> Eio_linux.Low_level.free_fixed block);
let block = Eio_linux.Low_level.alloc_fixed_or_wait () in
Eio_linux.Low_level.free_fixed block
let () = let () =
let open Alcotest in let open Alcotest in
run "eio_linux" [ run "eio_linux" [
@ -211,5 +256,7 @@ let () =
test_case "read_exact" `Quick test_read_exact; test_case "read_exact" `Quick test_read_exact;
test_case "expose_backend" `Quick test_expose_backend; test_case "expose_backend" `Quick test_expose_backend;
test_case "statx" `Quick test_statx; test_case "statx" `Quick test_statx;
test_case "signal_race" `Quick test_signal_race;
test_case "alloc-fixed-or-wait" `Quick test_alloc_fixed_or_wait;
]; ];
] ]

View File

@ -48,6 +48,11 @@ let run_event_loop fn x =
Unix.set_nonblock unix_fd; Unix.set_nonblock unix_fd;
continue k (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket) continue k (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket)
) )
| Eio_unix.Net.Import_socket_listening (sw, close_unix, unix_fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
Unix.set_nonblock unix_fd;
continue k (Net.listening_socket ~hook:Switch.null_hook fd)
)
| Eio_unix.Net.Import_socket_datagram (sw, close_unix, unix_fd) -> Some (fun k -> | Eio_unix.Net.Import_socket_datagram (sw, close_unix, unix_fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
Unix.set_nonblock unix_fd; Unix.set_nonblock unix_fd;

View File

@ -384,6 +384,27 @@ CAMLprim value caml_eio_posix_renameat(value v_old_fd, value v_old_path, value v
CAMLreturn(Val_unit); CAMLreturn(Val_unit);
} }
CAMLprim value caml_eio_posix_symlinkat(value v_old_path, value v_new_fd, value v_new_path) {
CAMLparam2(v_old_path, v_new_path);
size_t old_path_len = caml_string_length(v_old_path);
size_t new_path_len = caml_string_length(v_new_path);
char *old_path;
char *new_path;
int ret;
caml_unix_check_path(v_old_path, "symlinkat-old");
caml_unix_check_path(v_new_path, "symlinkat-new");
old_path = caml_stat_alloc(old_path_len + new_path_len + 2);
new_path = old_path + old_path_len + 1;
memcpy(old_path, String_val(v_old_path), old_path_len + 1);
memcpy(new_path, String_val(v_new_path), new_path_len + 1);
caml_enter_blocking_section();
ret = symlinkat(old_path, Int_val(v_new_fd), new_path);
caml_leave_blocking_section();
caml_stat_free_preserving_errno(old_path);
if (ret == -1) uerror("symlinkat", v_old_path);
CAMLreturn(Val_unit);
}
CAMLprim value caml_eio_posix_spawn(value v_errors, value v_actions) { CAMLprim value caml_eio_posix_spawn(value v_errors, value v_actions) {
CAMLparam1(v_actions); CAMLparam1(v_actions);
pid_t child_pid; pid_t child_pid;

View File

@ -94,6 +94,9 @@ end = struct
| None -> invalid_arg "Target is not an eio_posix directory!" | None -> invalid_arg "Target is not an eio_posix directory!"
| Some new_dir -> Err.run (Low_level.rename t.fd old_path new_dir) new_path | Some new_dir -> Err.run (Low_level.rename t.fd old_path new_dir) new_path
let symlink ~link_to t path =
Err.run (Low_level.symlink ~link_to t.fd) path
let open_dir t ~sw path = let open_dir t ~sw path =
let flags = Low_level.Open_flags.(rdonly + directory +? path) in let flags = Low_level.Open_flags.(rdonly + directory +? path) in
let fd = Err.run (Low_level.openat ~sw ~mode:0 t.fd path) flags in let fd = Err.run (Low_level.openat ~sw ~mode:0 t.fd path) flags in

View File

@ -8,7 +8,7 @@ let optional_flags = [
let () = let () =
C.main ~name:"discover" (fun c -> C.main ~name:"discover" (fun c ->
let c_flags = ["-D_LARGEFILE64_SOURCE"; "-D_XOPEN_SOURCE=700"; "-D_DARWIN_C_SOURCE"; "-D_GNU_SOURCE"] in let c_flags = ["-D_LARGEFILE64_SOURCE"; "-D_XOPEN_SOURCE=700"; "-D_DARWIN_C_SOURCE"; "-D_GNU_SOURCE"; "-D_BSD_SOURCE"] in
let includes = ["sys/types.h"; "sys/stat.h"; "fcntl.h"] in let includes = ["sys/types.h"; "sys/stat.h"; "fcntl.h"] in
let extra_flags, missing_defs = let extra_flags, missing_defs =
C.C_define.import c ~c_flags ~includes C.C_define.import c ~c_flags ~includes

View File

@ -263,7 +263,8 @@ module Resolve = struct
| new_base -> | new_base ->
state.dir_stack <- Tmp (new_base, state.dir_stack); state.dir_stack <- Tmp (new_base, state.dir_stack);
resolve state xs resolve state xs
| exception (Unix.Unix_error ((ENOTDIR | EMLINK | EUNKNOWNERR _), _, _) as e) -> | exception (Unix.Unix_error ((ELOOP | ENOTDIR | EMLINK | EUNKNOWNERR _), _, _) as e) ->
(* Note: Linux uses ELOOP or ENOTDIR. FreeBSD uses EMLINK. NetBSD uses EFTYPE. *)
match Eio_unix.Private.read_link_unix base x with match Eio_unix.Private.read_link_unix base x with
| target -> | target ->
decr_max_follows state x; decr_max_follows state x;
@ -414,6 +415,14 @@ let rename old_dir old_path new_dir new_path =
let new_dir = Option.value new_dir ~default:at_fdcwd in let new_dir = Option.value new_dir ~default:at_fdcwd in
eio_renameat old_dir old_path new_dir new_path eio_renameat old_dir old_path new_dir new_path
external eio_symlinkat : string -> Unix.file_descr -> string -> unit = "caml_eio_posix_symlinkat"
let symlink ~link_to new_dir new_path =
in_worker_thread "symlink" @@ fun () ->
Resolve.with_parent "symlink-new" new_dir new_path @@ fun new_dir new_path ->
let new_dir = Option.value new_dir ~default:at_fdcwd in
eio_symlinkat link_to new_dir new_path
let read_link dirfd path = let read_link dirfd path =
in_worker_thread "read_link" @@ fun () -> in_worker_thread "read_link" @@ fun () ->
Resolve.with_parent "read_link" dirfd path @@ fun dirfd path -> Resolve.with_parent "read_link" dirfd path @@ fun dirfd path ->
@ -549,6 +558,7 @@ module Process = struct
let t = let t =
let pid = let pid =
Fd.use_exn "errors-w" errors_w @@ fun errors_w -> Fd.use_exn "errors-w" errors_w @@ fun errors_w ->
Eio.Private.Trace.with_span "spawn" @@ fun () ->
eio_spawn errors_w c_actions eio_spawn errors_w c_actions
in in
Fd.close errors_w; Fd.close errors_w;

View File

@ -78,6 +78,10 @@ val mkdir : mode:int -> dir_fd -> string -> unit
val unlink : dir:bool -> dir_fd -> string -> unit val unlink : dir:bool -> dir_fd -> string -> unit
val rename : dir_fd -> string -> dir_fd -> string -> unit val rename : dir_fd -> string -> dir_fd -> string -> unit
val symlink : link_to:string -> dir_fd -> string -> unit
(** [symlink ~link_to dir path] will create a new symlink at [dir / path]
linking to [link_to]. *)
val readdir : dir_fd -> string -> string array val readdir : dir_fd -> string -> string array
val readv : fd -> Cstruct.t array -> int val readv : fd -> Cstruct.t array -> int

View File

@ -14,6 +14,7 @@ CAMLprim value caml_eio_posix_fdopendir(value);
CAMLprim value caml_eio_posix_mkdirat(value, value, value); CAMLprim value caml_eio_posix_mkdirat(value, value, value);
CAMLprim value caml_eio_posix_unlinkat(value, value, value); CAMLprim value caml_eio_posix_unlinkat(value, value, value);
CAMLprim value caml_eio_posix_renameat(value, value, value, value); CAMLprim value caml_eio_posix_renameat(value, value, value, value);
CAMLprim value caml_eio_posix_symlinkat(value, value, value);
CAMLprim value caml_eio_posix_make_stat(value); CAMLprim value caml_eio_posix_make_stat(value);
CAMLprim value caml_eio_posix_fstatat(value, value, value, value); CAMLprim value caml_eio_posix_fstatat(value, value, value, value);
CAMLprim value caml_eio_posix_fstat(value, value); CAMLprim value caml_eio_posix_fstat(value, value);

View File

@ -379,7 +379,7 @@ let run ~extra_effects t main x =
let `Exit_scheduler = let `Exit_scheduler =
let new_fiber = Fiber_context.make_root () in let new_fiber = Fiber_context.make_root () in
Domain_local_await.using Domain_local_await.using
~prepare_for_await:Eio.Private.Dla.prepare_for_await ~prepare_for_await:Eio_utils.Dla.prepare_for_await
~while_running:(fun () -> ~while_running:(fun () ->
fork ~new_fiber (fun () -> fork ~new_fiber (fun () ->
Eio_unix.Private.Thread_pool.run t.thread_pool @@ fun () -> Eio_unix.Private.Thread_pool.run t.thread_pool @@ fun () ->

View File

@ -3,8 +3,8 @@
(enabled_if (= %{os_type} "Unix")) (enabled_if (= %{os_type} "Unix"))
(deps (package eio_posix))) (deps (package eio_posix)))
(test (tests
(name open_beneath) (names open_beneath test_await)
(package eio_posix) (package eio_posix)
(build_if (= %{os_type} "Unix")) (build_if (= %{os_type} "Unix"))
(libraries eio_posix)) (libraries eio_posix))

View File

@ -0,0 +1,25 @@
open Eio.Std
let () =
Eio_posix.run @@ fun _ ->
let a, b = Unix.(socketpair PF_UNIX SOCK_STREAM 0) in
(* Start awaiting readable/writable state, but cancel immediately. *)
try
Eio.Cancel.sub (fun cc ->
Fiber.all [
(fun () -> Eio_unix.await_readable a);
(fun () -> Eio_unix.await_writable b);
(fun () -> Eio.Cancel.cancel cc Exit);
];
assert false
)
with Eio.Cancel.Cancelled _ ->
(* Now wait for something else. Will fail if the old FDs are still being waited on. *)
let c, d = Unix.(socketpair PF_UNIX SOCK_STREAM 0) in
Unix.close a;
Unix.close b;
Fiber.first
(fun () -> Eio_unix.await_readable c)
(fun () -> Eio_unix.await_writable d);
Unix.close c;
Unix.close d

View File

@ -48,6 +48,11 @@ let run_event_loop fn x =
(try Unix.set_nonblock unix_fd with Unix.Unix_error (Unix.ENOTSOCK, _, _) -> ()); (try Unix.set_nonblock unix_fd with Unix.Unix_error (Unix.ENOTSOCK, _, _) -> ());
continue k (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket) continue k (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket)
) )
| Eio_unix.Net.Import_socket_listening (sw, close_unix, unix_fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
Unix.set_nonblock unix_fd;
continue k (Net.listening_socket ~hook:Switch.null_hook fd)
)
| Eio_unix.Net.Import_socket_datagram (sw, close_unix, unix_fd) -> Some (fun k -> | Eio_unix.Net.Import_socket_datagram (sw, close_unix, unix_fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
Unix.set_nonblock unix_fd; Unix.set_nonblock unix_fd;

View File

@ -6,7 +6,7 @@
(foreign_stubs (foreign_stubs
(language c) (language c)
(include_dirs ../lib_eio/unix/include) (include_dirs ../lib_eio/unix/include)
(names eio_windows_stubs eio_windows_cstruct_stubs)) (names eio_windows_stubs))
(c_library_flags :standard -lbcrypt -lntdll) (c_library_flags :standard -lbcrypt -lntdll)
(libraries eio eio.unix eio.utils fmt)) (libraries eio eio.unix eio.utils fmt))

View File

@ -1,149 +0,0 @@
/* From mirage/ocaml-cstruct
Copyright (c) 2012 Anil Madhavapeddy <anil@recoil.org>
Copyright (c) 2012 Pierre Chambart
Copyright (c) Christiano F. Haesbaert <haesbaert@haesbaert.org>
Copyright (c) Citrix Inc
Copyright (c) David Sheets <sheets@alum.mit.edu>
Copyright (c) Drup <drupyog@zoho.com>
Copyright (c) Hannes Mehnert <hannes@mehnert.org>
Copyright (c) Jeremy Yallop <yallop@gmail.com>
Copyright (c) Mindy Preston <meetup@yomimono.org>
Copyright (c) Nicolas Ojeda Bar <n.oje.bar@gmail.com>
Copyright (c) Richard Mortier <mort@cantab.net>
Copyright (c) Rudi Grinberg <rudi.grinberg@gmail.com>
Copyright (c) Thomas Gazagnaire <thomas@gazagnaire.com>
Copyright (c) Thomas Leonard <talex5@gmail.com>
Copyright (c) Vincent Bernardoff <vb@luminar.eu.org>
Copyright (c) pqwy <david@numm.org>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include <caml/mlvalues.h>
#include <caml/memory.h>
#include <caml/custom.h>
#include <caml/callback.h>
#include <caml/alloc.h>
#include <caml/unixsupport.h>
#include <caml/bigarray.h>
#include <caml/threads.h>
#include <caml/fail.h>
#include <stdio.h>
#include <errno.h>
CAMLprim value eio_windows_cstruct_read(value val_fd, value val_c)
{
CAMLparam2(val_fd, val_c);
CAMLlocal3(val_buf, val_ofs, val_len);
uint8_t *buf;
size_t len;
ssize_t n = 0;
int win32err = 0;
SOCKET s;
HANDLE h;
DWORD numread;
int ok;
val_buf = Field(val_c, 0);
val_ofs = Field(val_c, 1);
val_len = Field(val_c, 2);
buf = (uint8_t *)Caml_ba_data_val(val_buf) + Long_val(val_ofs);
len = (size_t)Long_val(val_len);
switch (Descr_kind_val(val_fd))
{
case KIND_SOCKET:
s = Socket_val(val_fd);
caml_release_runtime_system();
n = recv(s, buf, len, 0);
win32err = WSAGetLastError();
caml_acquire_runtime_system();
if (n == SOCKET_ERROR)
{
win32_maperr(win32err);
uerror("stub_cstruct_read", Nothing);
}
break;
case KIND_HANDLE:
h = Handle_val(val_fd);
caml_release_runtime_system();
ok = ReadFile(h, buf, len, &numread, NULL);
win32err = GetLastError();
n = numread;
caml_acquire_runtime_system();
if (!ok)
{
win32_maperr(win32err);
uerror("stub_cstruct_read", Nothing);
}
break;
default:
caml_failwith("unknown Descr_kind_val");
}
CAMLreturn(Val_int(n));
}
CAMLprim value eio_windows_cstruct_write(value val_fd, value val_c)
{
CAMLparam2(val_fd, val_c);
CAMLlocal3(val_buf, val_ofs, val_len);
val_buf = Field(val_c, 0);
val_ofs = Field(val_c, 1);
val_len = Field(val_c, 2);
void *buf = (char *)Caml_ba_data_val(val_buf) + Long_val(val_ofs);
size_t len = Long_val(val_len);
ssize_t n = 0;
int win32err = 0;
switch (Descr_kind_val(val_fd))
{
case KIND_SOCKET:
SOCKET s = Socket_val(val_fd);
caml_release_runtime_system();
n = send(s, buf, len, 0);
win32err = WSAGetLastError();
caml_acquire_runtime_system();
if (n == SOCKET_ERROR)
{
win32_maperr(win32err);
unix_error(errno, "stub_cstruct_write", Nothing);
}
break;
case KIND_HANDLE:
HANDLE h = Handle_val(val_fd);
DWORD numwritten;
caml_release_runtime_system();
int ok = WriteFile(h, buf, len, &numwritten, NULL);
win32err = GetLastError();
n = numwritten;
caml_acquire_runtime_system();
if (!ok)
{
win32_maperr(win32err);
uerror("stub_cstruct_write", Nothing);
}
break;
default:
caml_failwith("unknown Descr_kind_val");
}
CAMLreturn(Val_int(n));
}

View File

@ -237,7 +237,7 @@ CAMLprim value caml_eio_windows_unlinkat(value v_dirfd, value v_pathname, value
if (!NT_SUCCESS(r)) { if (!NT_SUCCESS(r)) {
caml_win32_maperr(RtlNtStatusToDosError(r)); caml_win32_maperr(RtlNtStatusToDosError(r));
uerror("openat", Nothing); uerror("openat", v_pathname);
} }
// Now close the file to delete it // Now close the file to delete it
@ -252,6 +252,11 @@ CAMLprim value caml_eio_windows_renameat(value v_old_fd, value v_old_path, value
uerror("renameat is not supported on windows yet", Nothing); uerror("renameat is not supported on windows yet", Nothing);
} }
CAMLprim value caml_eio_windows_symlinkat(value v_old_path, value v_new_fd, value v_new_path)
{
uerror("symlinkat is not supported on windows yet", Nothing);
}
CAMLprim value caml_eio_windows_spawn(value v_errors, value v_actions) CAMLprim value caml_eio_windows_spawn(value v_errors, value v_actions)
{ {
uerror("processes are not supported on windows yet", Nothing); uerror("processes are not supported on windows yet", Nothing);

View File

@ -20,7 +20,7 @@ let wrap code name arg =
| ENOENT -> Eio.Fs.err (Not_found e) | ENOENT -> Eio.Fs.err (Not_found e)
| EXDEV | EACCES | EPERM -> Eio.Fs.err (Permission_denied e) | EXDEV | EACCES | EPERM -> Eio.Fs.err (Permission_denied e)
| ECONNREFUSED -> Eio.Net.err (Connection_failure (Refused e)) | ECONNREFUSED -> Eio.Net.err (Connection_failure (Refused e))
| ECONNRESET | EPIPE -> Eio.Net.err (Connection_reset e) | ECONNRESET | EPIPE | ECONNABORTED -> Eio.Net.err (Connection_reset e)
| _ -> unclassified_error e | _ -> unclassified_error e
let run fn x = let run fn x =

View File

@ -82,7 +82,7 @@ end = struct
let dir = resolve t dir in let dir = resolve t dir in
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
let open Low_level in let open Low_level in
let dirfd = Low_level.openat ~sw ~nofollow:true dir Flags.Open.(generic_read + synchronise) Flags.Disposition.(open_if) Flags.Create.(directory) in let dirfd = Err.run (Low_level.openat ~sw ~nofollow:true dir Flags.Open.(generic_read + synchronise) Flags.Disposition.(open_if)) Flags.Create.(directory) in
fn (Some dirfd) leaf fn (Some dirfd) leaf
) )
) else fn None path ) else fn None path
@ -172,6 +172,10 @@ end = struct
with_parent_dir new_dir new_path @@ fun new_dir new_path -> with_parent_dir new_dir new_path @@ fun new_dir new_path ->
Err.run (Low_level.rename ?old_dir old_path ?new_dir) new_path Err.run (Low_level.rename ?old_dir old_path ?new_dir) new_path
let symlink ~link_to t path =
with_parent_dir t path @@ fun dirfd path ->
Err.run (Low_level.symlink ~link_to dirfd) path
let close t = t.closed <- true let close t = t.closed <- true
let open_dir t ~sw path = let open_dir t ~sw path =

View File

@ -39,17 +39,25 @@ let rec do_nonblocking ty fn fd =
do_nonblocking ty fn fd do_nonblocking ty fn fd
let read fd buf start len = let read fd buf start len =
await_readable fd;
Fd.use_exn "read" fd @@ fun fd -> Fd.use_exn "read" fd @@ fun fd ->
do_nonblocking Read (fun fd -> Unix.read fd buf start len) fd do_nonblocking Read (fun fd -> Unix.read fd buf start len) fd
let read_cstruct fd buf = let read_cstruct fd (buf:Cstruct.t) =
await_readable fd;
Fd.use_exn "read_cstruct" fd @@ fun fd -> Fd.use_exn "read_cstruct" fd @@ fun fd ->
do_nonblocking Read (fun fd -> Unix_cstruct.read fd buf) fd do_nonblocking Read (fun fd -> Unix.read_bigarray fd buf.buffer buf.off buf.len) fd
let write fd buf start len = let write fd buf start len =
await_writable fd;
Fd.use_exn "write" fd @@ fun fd -> Fd.use_exn "write" fd @@ fun fd ->
do_nonblocking Write (fun fd -> Unix.write fd buf start len) fd do_nonblocking Write (fun fd -> Unix.write fd buf start len) fd
let write_cstruct fd (buf:Cstruct.t) =
await_writable fd;
Fd.use_exn "write_cstruct" fd @@ fun fd ->
do_nonblocking Write (fun fd -> Unix.write_bigarray fd buf.buffer buf.off buf.len) fd
let sleep_until time = let sleep_until time =
Sched.enter @@ fun t k -> Sched.enter @@ fun t k ->
Sched.await_timeout t k time Sched.await_timeout t k time
@ -148,8 +156,11 @@ let readv fd bufs =
do_nonblocking Read (fun fd -> eio_readv fd bufs) fd do_nonblocking Read (fun fd -> eio_readv fd bufs) fd
let writev fd bufs = let writev fd bufs =
Fd.use_exn "writev" fd @@ fun fd -> let rec loop buf = if Cstruct.length buf > 0 then begin
do_nonblocking Write (fun fd -> Unix_cstruct.writev fd bufs) fd let n = write_cstruct fd buf in
loop @@ Cstruct.shift buf n
end in
List.iter loop bufs
let preadv ~file_offset fd bufs = let preadv ~file_offset fd bufs =
Fd.use_exn "preadv" fd @@ fun fd -> Fd.use_exn "preadv" fd @@ fun fd ->
@ -234,6 +245,14 @@ let rename ?old_dir old_path ?new_dir new_path =
in_worker_thread @@ fun () -> in_worker_thread @@ fun () ->
eio_renameat old_dir old_path new_dir new_path eio_renameat old_dir old_path new_dir new_path
external eio_symlinkat : string -> Unix.file_descr option -> string -> unit = "caml_eio_windows_symlinkat"
let symlink ~link_to new_dir new_path =
with_dirfd "symlink-new" new_dir @@ fun new_dir ->
in_worker_thread @@ fun () ->
eio_symlinkat link_to new_dir new_path
let lseek fd off cmd = let lseek fd off cmd =
Fd.use_exn "lseek" fd @@ fun fd -> Fd.use_exn "lseek" fd @@ fun fd ->
let cmd = let cmd =

View File

@ -22,6 +22,7 @@ val sleep_until : Mtime.t -> unit
val read : fd -> bytes -> int -> int -> int val read : fd -> bytes -> int -> int -> int
val read_cstruct : fd -> Cstruct.t -> int val read_cstruct : fd -> Cstruct.t -> int
val write : fd -> bytes -> int -> int -> int val write : fd -> bytes -> int -> int -> int
val write_cstruct : fd -> Cstruct.t -> int
val socket : sw:Switch.t -> Unix.socket_domain -> Unix.socket_type -> int -> fd val socket : sw:Switch.t -> Unix.socket_domain -> Unix.socket_type -> int -> fd
val connect : fd -> Unix.sockaddr -> unit val connect : fd -> Unix.sockaddr -> unit
@ -48,6 +49,10 @@ val mkdir : ?dirfd:fd -> ?nofollow:bool -> mode:int -> string -> unit
val unlink : ?dirfd:fd -> dir:bool -> string -> unit val unlink : ?dirfd:fd -> dir:bool -> string -> unit
val rename : ?old_dir:fd -> string -> ?new_dir:fd -> string -> unit val rename : ?old_dir:fd -> string -> ?new_dir:fd -> string -> unit
val symlink : link_to:string -> fd option -> string -> unit
(** [symlink ~link_to dir path] will create a new symlink at [dir / path]
linking to [link_to]. *)
val readdir : string -> string array val readdir : string -> string array
val readv : fd -> Cstruct.t array -> int val readv : fd -> Cstruct.t array -> int

View File

@ -83,24 +83,23 @@ let datagram_handler = Eio_unix.Pi.datagram_handler (module Datagram_socket)
let datagram_socket fd = let datagram_socket fd =
Eio.Resource.T (fd, datagram_handler) Eio.Resource.T (fd, datagram_handler)
(* https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml *)
let getaddrinfo ~service node = let getaddrinfo ~service node =
let to_eio_sockaddr_t {Unix.ai_family; ai_addr; ai_socktype; ai_protocol; _ } = (* OCaml's [Unix.getaddrinfo] on Windows doesn't set [ai_protocol] to
match ai_family, ai_socktype, ai_addr with anything useful, so you can't tell which addresses are TCP and which are
| (Unix.PF_INET | PF_INET6), UDP. So, do two separate queries. *)
(Unix.SOCK_STREAM | SOCK_DGRAM), let get ty k =
Unix.ADDR_INET (inet_addr,port) -> ( Unix.getaddrinfo node service [AI_SOCKTYPE ty]
match ai_protocol with |> List.filter_map (function
| 6 -> Some (`Tcp (Eio_unix.Net.Ipaddr.of_unix inet_addr, port)) | {Unix.ai_addr = ADDR_INET (host, port); _} ->
| 17 -> Some (`Udp (Eio_unix.Net.Ipaddr.of_unix inet_addr, port)) Some (k (Eio_unix.Net.Ipaddr.of_unix host, port))
| _ -> None)
| _ -> None | _ -> None
)
in in
Err.run (Eio_unix.run_in_systhread ~label:"getaddrinfo") @@ fun () -> Err.run (Eio_unix.run_in_systhread ~label:"getaddrinfo") @@ fun () ->
let rec aux () = let rec aux () =
try try
Unix.getaddrinfo node service [] get SOCK_STREAM (fun x -> `Tcp x) @
|> List.filter_map to_eio_sockaddr_t get SOCK_DGRAM (fun x -> `Udp x)
with Unix.Unix_error (EINTR, _, _) -> aux () with Unix.Unix_error (EINTR, _, _) -> aux ()
in in
aux () aux ()

View File

@ -271,6 +271,8 @@ let await_readable t (k : unit Suspended.t) fd =
if was_empty then update t waiters fd; if was_empty then update t waiters fd;
Fiber_context.set_cancel_fn k.fiber (fun ex -> Fiber_context.set_cancel_fn k.fiber (fun ex ->
Lwt_dllist.remove node; Lwt_dllist.remove node;
if Lwt_dllist.is_empty waiters.read then
update t waiters fd;
t.active_ops <- t.active_ops - 1; t.active_ops <- t.active_ops - 1;
enqueue_failed_thread t k ex enqueue_failed_thread t k ex
); );
@ -287,6 +289,8 @@ let await_writable t (k : unit Suspended.t) fd =
if was_empty then update t waiters fd; if was_empty then update t waiters fd;
Fiber_context.set_cancel_fn k.fiber (fun ex -> Fiber_context.set_cancel_fn k.fiber (fun ex ->
Lwt_dllist.remove node; Lwt_dllist.remove node;
if Lwt_dllist.is_empty waiters.write then
update t waiters fd;
t.active_ops <- t.active_ops - 1; t.active_ops <- t.active_ops - 1;
enqueue_failed_thread t k ex enqueue_failed_thread t k ex
); );
@ -370,7 +374,7 @@ let run ~extra_effects t main x =
let `Exit_scheduler = let `Exit_scheduler =
let new_fiber = Fiber_context.make_root () in let new_fiber = Fiber_context.make_root () in
Domain_local_await.using Domain_local_await.using
~prepare_for_await:Eio.Private.Dla.prepare_for_await ~prepare_for_await:Eio_utils.Dla.prepare_for_await
~while_running:(fun () -> ~while_running:(fun () ->
fork ~new_fiber (fun () -> fork ~new_fiber (fun () ->
Eio_unix.Private.Thread_pool.run t.thread_pool @@ fun () -> Eio_unix.Private.Thread_pool.run t.thread_pool @@ fun () ->

View File

@ -1,3 +1,5 @@
open Eio.Std
module Timeout = struct module Timeout = struct
let test clock () = let test clock () =
let t0 = Unix.gettimeofday () in let t0 = Unix.gettimeofday () in
@ -48,6 +50,35 @@ module Dla = struct
] ]
end end
module Await_fd = struct
let test_cancel () =
let a, b = Unix.(socketpair PF_UNIX SOCK_STREAM 0) in
(* Start awaiting readable/writable state, but cancel immediately. *)
try
Eio.Cancel.sub (fun cc ->
Fiber.all [
(fun () -> Eio_unix.await_readable a);
(fun () -> Eio_unix.await_writable b);
(fun () -> Eio.Cancel.cancel cc Exit);
];
assert false
)
with Eio.Cancel.Cancelled _ ->
(* Now wait for something else. Will fail if the old FDs are still being waited on. *)
let c, d = Unix.(socketpair PF_UNIX SOCK_STREAM 0) in
Unix.close a;
Unix.close b;
Fiber.first
(fun () -> Eio_unix.await_readable c)
(fun () -> Eio_unix.await_writable d);
Unix.close c;
Unix.close d
let tests = [
"cancel", `Quick, test_cancel;
]
end
let () = let () =
Eio_windows.run @@ fun env -> Eio_windows.run @@ fun env ->
@ -56,5 +87,6 @@ let () =
"fs", Test_fs.tests env; "fs", Test_fs.tests env;
"timeout", Timeout.tests env; "timeout", Timeout.tests env;
"random", Random.tests env; "random", Random.tests env;
"dla", Dla.tests "dla", Dla.tests;
"await", Await_fd.tests;
] ]

View File

@ -158,6 +158,9 @@ let test_symlink env () =
Unix.mkdir "another" 0o700; Unix.mkdir "another" 0o700;
print_endline @@ Unix.realpath "to-subdir" |} print_endline @@ Unix.realpath "to-subdir" |}
*) *)
if not (Unix.has_symlink ()) then
Printf.printf "Skipping test_symlink on systems that don't support symlinks.\n"
else
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
try_mkdir (cwd / "sandbox"); try_mkdir (cwd / "sandbox");
Unix.symlink ~to_dir:true ".." "sandbox\\to-root"; Unix.symlink ~to_dir:true ".." "sandbox\\to-root";
@ -277,4 +280,5 @@ let tests env = [
"unlink", `Quick, test_unlink env; "unlink", `Quick, test_unlink env;
"failing-unlink", `Quick, try_failing_unlink env; "failing-unlink", `Quick, try_failing_unlink env;
"rmdir", `Quick, test_remove_dir env; "rmdir", `Quick, test_remove_dir env;
"mkdirs", `Quick, test_mkdirs env;
] ]

View File

@ -89,11 +89,17 @@ let try_stat path =
traceln "%a -> %s" Eio.Path.pp path a traceln "%a -> %s" Eio.Path.pp path a
else else
traceln "%a -> %s / %s" Eio.Path.pp path a b traceln "%a -> %s / %s" Eio.Path.pp path a b
let try_symlink ~link_to path =
match Path.symlink ~link_to path with
| s -> traceln "symlink %a -> %S" Path.pp path link_to
| exception ex -> traceln "@[<h>%a@]" Eio.Exn.pp ex
``` ```
# Basic test cases # Basic test cases
Creating a file and reading it back: Creating a file and reading it back:
```ocaml ```ocaml
# run ~clear:["test-file"] @@ fun env -> # run ~clear:["test-file"] @@ fun env ->
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
@ -104,6 +110,7 @@ Creating a file and reading it back:
``` ```
Check the file got the correct permissions (subject to the umask set above): Check the file got the correct permissions (subject to the umask set above):
```ocaml ```ocaml
# Printf.printf "Perm = %o\n" ((Unix.stat "test-file").st_perm);; # Printf.printf "Perm = %o\n" ((Unix.stat "test-file").st_perm);;
Perm = 644 Perm = 644
@ -113,6 +120,7 @@ Perm = 644
# Sandboxing # Sandboxing
Trying to use cwd to access a file outside of that subtree fails: Trying to use cwd to access a file outside of that subtree fails:
```ocaml ```ocaml
# run @@ fun env -> # run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
@ -123,6 +131,7 @@ Exception: Eio.Io Fs Permission_denied _,
``` ```
Trying to use cwd to access an absolute path fails: Trying to use cwd to access an absolute path fails:
```ocaml ```ocaml
# run @@ fun env -> # run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
@ -135,6 +144,7 @@ Exception: Eio.Io Fs Permission_denied _,
# Creation modes # Creation modes
Exclusive create fails if already exists: Exclusive create fails if already exists:
```ocaml ```ocaml
# run @@ fun env -> # run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
@ -146,6 +156,7 @@ Exception: Eio.Io Fs Already_exists _,
``` ```
If-missing create succeeds if already exists: If-missing create succeeds if already exists:
```ocaml ```ocaml
# run @@ fun env -> # run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
@ -158,6 +169,7 @@ If-missing create succeeds if already exists:
``` ```
Truncate create succeeds if already exists, and truncates: Truncate create succeeds if already exists, and truncates:
```ocaml ```ocaml
# run @@ fun env -> # run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
@ -172,6 +184,7 @@ Truncate create succeeds if already exists, and truncates:
``` ```
Error if no create and doesn't exist: Error if no create and doesn't exist:
```ocaml ```ocaml
# run @@ fun env -> # run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
@ -183,6 +196,7 @@ Exception: Eio.Io Fs Not_found _,
``` ```
Appending to an existing file: Appending to an existing file:
```ocaml ```ocaml
# run @@ fun env -> # run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
@ -215,12 +229,13 @@ Appending to an existing file:
``` ```
Creating directories with nesting, symlinks, etc: Creating directories with nesting, symlinks, etc:
```ocaml ```ocaml
# run ~clear:["to-subdir"; "to-root"; "dangle"] @@ fun env -> # run ~clear:["to-subdir"; "to-root"; "dangle"] @@ fun env ->
Unix.symlink "/" "to-root";
Unix.symlink "subdir" "to-subdir";
Unix.symlink "foo" "dangle";
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
Path.symlink ~link_to:"/" (cwd / "to-root");
Path.symlink ~link_to:"subdir" (cwd / "to-subdir");
Path.symlink ~link_to:"foo" (cwd / "dangle");
try_mkdir (cwd / "subdir"); try_mkdir (cwd / "subdir");
try_mkdir (cwd / "to-subdir/nested"); try_mkdir (cwd / "to-subdir/nested");
try_mkdir (cwd / "to-root/tmp/foo"); try_mkdir (cwd / "to-root/tmp/foo");
@ -384,10 +399,10 @@ Reads and writes follow symlinks, but unlink operates on the symlink itself:
let file2 = cwd / "file2" in let file2 = cwd / "file2" in
try_write_file ~create:(`Exclusive 0o600) file1 "data1"; try_write_file ~create:(`Exclusive 0o600) file1 "data1";
try_write_file ~create:(`Exclusive 0o400) file2 "data2"; try_write_file ~create:(`Exclusive 0o400) file2 "data2";
Unix.symlink "dir1/file1" "link1"; Path.symlink ~link_to:"dir1/file1" (cwd / "link1");
Unix.symlink "../file2" "dir1/link2"; Path.symlink ~link_to:"../file2" (cwd / "dir1/link2");
Unix.symlink "dir1" "linkdir"; Path.symlink ~link_to:"dir1" (cwd / "linkdir");
Unix.symlink "/" "linkroot"; Path.symlink ~link_to:"/" (cwd / "linkroot");
try_read_file file1; try_read_file file1;
try_read_file (cwd / "link1"); try_read_file (cwd / "link1");
try_read_file (cwd / "linkdir" / "file1"); try_read_file (cwd / "linkdir" / "file1");
@ -504,6 +519,7 @@ Removing something that doesn't exist or is out of scope:
# Limiting to a subdirectory # Limiting to a subdirectory
Create a sandbox, write a file with it, then read it from outside: Create a sandbox, write a file with it, then read it from outside:
```ocaml ```ocaml
# run ~clear:["sandbox"] @@ fun env -> # run ~clear:["sandbox"] @@ fun env ->
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
@ -540,10 +556,10 @@ Create a sandbox, write a file with it, then read it from outside:
reject (cwd / "/"); reject (cwd / "/");
test (cwd / "foo/bar/.."); test (cwd / "foo/bar/..");
test (fs / "foo/bar"); test (fs / "foo/bar");
Unix.symlink ".." "foo/up"; Path.symlink ~link_to:".." (cwd / "foo/up");
test (cwd / "foo/up/foo/bar"); test (cwd / "foo/up/foo/bar");
reject (cwd / "foo/up/../bar"); reject (cwd / "foo/up/../bar");
Unix.symlink "/" "foo/root"; Path.symlink ~link_to:"/" (cwd / "foo/root");
reject (cwd / "foo/root/.."); reject (cwd / "foo/root/..");
reject (cwd / "missing"); reject (cwd / "missing");
+open_dir <cwd:foo/bar> -> OK +open_dir <cwd:foo/bar> -> OK
@ -566,6 +582,7 @@ Create a sandbox, write a file with it, then read it from outside:
We create a directory and chdir into it. We create a directory and chdir into it.
Using `cwd` we can't access the parent, but using `fs` we can: Using `cwd` we can't access the parent, but using `fs` we can:
```ocaml ```ocaml
# run ~clear:["fs-test"; "outside-cwd"] @@ fun env -> # run ~clear:["fs-test"; "outside-cwd"] @@ fun env ->
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
@ -604,7 +621,7 @@ Reading directory entries under `cwd` and outside of `cwd`.
try_read_dir (tmpdir / "."); try_read_dir (tmpdir / ".");
try_read_dir (tmpdir / ".."); try_read_dir (tmpdir / "..");
try_read_dir (tmpdir / "test-3"); try_read_dir (tmpdir / "test-3");
Unix.symlink "test-1" "readdir/link-1"; Path.symlink ~link_to:"test-1" (cwd / "readdir/link-1");
try_read_dir (tmpdir / "link-1"); try_read_dir (tmpdir / "link-1");
+mkdir <cwd:readdir> -> ok +mkdir <cwd:readdir> -> ok
+mkdir <readdir:test-1> -> ok +mkdir <readdir:test-1> -> ok
@ -650,6 +667,29 @@ Exception: Eio.Io Fs Permission_denied _,
opening <cwd:/dev/null> opening <cwd:/dev/null>
``` ```
Symlinking and sandboxing:
```ocaml
# run ~clear:["hello.txt"; "world.txt"] @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
Path.save ~create:(`Exclusive 0o600) (cwd / "hello.txt") "Hello World!";
try_symlink ~link_to:"hello.txt" (cwd / "../world.txt");
try_symlink ~link_to:"hello.txt" (cwd / "/world.txt");
try_symlink ~link_to:"hello.txt" (cwd / "world.txt");
traceln "world.txt -> hello.txt: %s" (Path.load (cwd / "world.txt"));
try_symlink ~link_to:"hello.txt" (cwd / "world.txt");
try_symlink ~link_to:"/" (cwd / "root");
try_read_dir (cwd / "root");;
+Eio.Io Fs Permission_denied _, creating symlink <cwd:../world.txt> -> hello.txt
+Eio.Io Fs Permission_denied _, creating symlink <cwd:/world.txt> -> hello.txt
+symlink <cwd:world.txt> -> "hello.txt"
+world.txt -> hello.txt: Hello World!
+Eio.Io Fs Already_exists _, creating symlink <cwd:world.txt> -> hello.txt
+symlink <cwd:root> -> "/"
+Eio.Io Fs Permission_denied _, reading directory <cwd:root>
- : unit = ()
```
## Streamling lines ## Streamling lines
```ocaml ```ocaml
@ -781,15 +821,15 @@ Unconfined:
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
try_mkdir (cwd / "stat_subdir2"); try_mkdir (cwd / "stat_subdir2");
Unix.symlink "stat_subdir2" "symlink"; Path.symlink ~link_to:"stat_subdir2" (cwd / "symlink");
Unix.symlink "missing" "broken-symlink"; Path.symlink ~link_to:"missing" (cwd / "broken-symlink");
try_stat (cwd / "stat_subdir2"); try_stat (cwd / "stat_subdir2");
try_stat (cwd / "symlink"); try_stat (cwd / "symlink");
try_stat (cwd / "broken-symlink"); try_stat (cwd / "broken-symlink");
try_stat cwd; try_stat cwd;
try_stat (cwd / ".."); try_stat (cwd / "..");
try_stat (cwd / "stat_subdir2/.."); try_stat (cwd / "stat_subdir2/..");
Unix.symlink ".." "parent-symlink"; Path.symlink ~link_to:".." (cwd / "parent-symlink");
try_stat (cwd / "parent-symlink"); try_stat (cwd / "parent-symlink");
try_stat (cwd / "missing1" / "missing2"); try_stat (cwd / "missing1" / "missing2");
+mkdir <cwd:stat_subdir2> -> ok +mkdir <cwd:stat_subdir2> -> ok
@ -811,7 +851,7 @@ Unconfined:
let fs = Eio.Stdenv.fs env in let fs = Eio.Stdenv.fs env in
let cwd = Eio.Stdenv.cwd env in let cwd = Eio.Stdenv.cwd env in
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
Unix.symlink "file" "symlink"; Path.symlink ~link_to:"file" (cwd / "symlink");
try_read_link (cwd / "symlink"); try_read_link (cwd / "symlink");
try_read_link (fs / "symlink"); try_read_link (fs / "symlink");
try_write_file (cwd / "file") "data" ~create:(`Exclusive 0o600); try_write_file (cwd / "file") "data" ~create:(`Exclusive 0o600);
@ -945,3 +985,29 @@ Exception: Failure "Simulated error".
+seek from end: 9 +seek from end: 9
- : unit = () - : unit = ()
``` ```
# Extending paths
```ocaml
# run @@ fun env ->
let base = fst env#cwd in
List.iter (fun (a, b) -> traceln "%S / %S = %S" a b (snd ((base, a) / b))) [
"foo", "bar";
"foo/", "bar";
"foo", "/bar";
"foo", "";
"foo/", "";
"", "";
"", "bar";
"/", "";
]
+"foo" / "bar" = "foo/bar"
+"foo/" / "bar" = "foo/bar"
+"foo" / "/bar" = "/bar"
+"foo" / "" = "foo/"
+"foo/" / "" = "foo/"
+"" / "" = ""
+"" / "bar" = "bar"
+"/" / "" = "/"
- : unit = ()
```

View File

@ -373,6 +373,29 @@ Wrapping a Unix FD as an Eio stream socket:
- : unit = () - : unit = ()
``` ```
Wrapping a Unix FD as a listening Eio socket:
```ocaml
# run @@ fun ~net sw ->
let l = Unix.(socket PF_INET SOCK_STREAM 0) in
Unix.bind l (Unix.ADDR_INET (Unix.inet_addr_loopback, 8082));
Unix.listen l 40;
let l = Eio_unix.Net.import_socket_listening ~sw ~close_unix:true l in
Fiber.both
(fun () -> run_server ~sw l)
(fun () ->
run_client ~sw ~net ~addr:(`Tcp (Eio.Net.Ipaddr.V4.loopback, 8082));
traceln "Client finished - cancelling server";
raise Graceful_shutdown
);;
+Connecting to server...
+Server accepted connection from client
+Server received: "Hello from client"
+Client received: "Bye"
+Client finished - cancelling server
Exception: Graceful_shutdown.
```
Wrapping a Unix FD as an datagram Eio socket: Wrapping a Unix FD as an datagram Eio socket:
```ocaml ```ocaml
@ -597,7 +620,9 @@ Exception: Eio.Io Fs Not_found _,
```ocaml ```ocaml
# Eio_main.run @@ fun env -> # Eio_main.run @@ fun env ->
let sockaddr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 80) in let sockaddr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 80) in
Eio.Net.getnameinfo env#net sockaddr;; let (host, service) = Eio.Net.getnameinfo env#net sockaddr in
let service = if service = "www" then "http" else service in (* OpenBSD *)
(host, service)
- : string * string = ("localhost", "http") - : string * string = ("localhost", "http")
``` ```

View File

@ -77,6 +77,37 @@ Two uses with a capacity of 2; they run in parallel:
- : unit = () - : unit = ()
``` ```
Capacity of 1; two uses that cannot block and two normal uses; first 2 are parallel, next 2 are sequential.
Note that the pool always suspends the calling fiber when creating a new slot,
even if the fiber ends up providing the new slot to itself,
which is why the items get assigned out of order in this test.
```ocaml
# Eio_mock.Backend.run @@ fun () ->
let p0, r0 = Promise.create () in
let p1, r1 = Promise.create () in
let t = create 1 [p0; p1] ~dispose in
Fiber.all [
(fun () -> P.use t ~never_block:true (fun x -> traceln "A: using item %d" x; Fiber.yield (); traceln "A done"));
(fun () -> P.use t ~never_block:true (fun x -> traceln "B: using item %d" x; Fiber.yield (); traceln "B done"));
(fun () -> P.use t (fun x -> traceln "C: using item %d" x; Fiber.yield (); traceln "C done"));
(fun () -> P.use t (fun x -> traceln "D: using item %d" x; Fiber.yield (); traceln "D done"));
(fun () -> Promise.resolve r0 (Ok 0); Promise.resolve r1 (Ok 1));
];
+Creating item 0
+Creating item 1
+A: using item 1
+B: using item 0
+A done
+B done
+disposing 0
+C: using item 1
+C done
+D: using item 1
+D done
- : unit = ()
```
## Cancellation ## Cancellation
```ocaml ```ocaml

View File

@ -145,24 +145,24 @@ If a command fails, we get shown the arguments (quoted if necessary):
```ocaml ```ocaml
# run @@ fun mgr env -> # run @@ fun mgr env ->
Process.run mgr ["bash"; "-c"; "exit 3"; ""; "foo"; "\"bar\""];; Process.run mgr ["sh"; "-c"; "exit 3"; ""; "foo"; "\"bar\""];;
Exception: Exception:
Eio.Io Process Child_error Exited (code 3), Eio.Io Process Child_error Exited (code 3),
running command: bash -c "exit 3" "" foo "\"bar\"" running command: sh -c "exit 3" "" foo "\"bar\""
``` ```
Exit code success can be determined by is_success (Process.run): Exit code success can be determined by is_success (Process.run):
```ocaml ```ocaml
# run @@ fun mgr env -> # run @@ fun mgr env ->
Process.run ~is_success:(Int.equal 3) mgr ["bash"; "-c"; "exit 3"];; Process.run ~is_success:(Int.equal 3) mgr ["sh"; "-c"; "exit 3"];;
- : unit = () - : unit = ()
# run @@ fun mgr env -> # run @@ fun mgr env ->
Process.run ~is_success:(Int.equal 3) mgr ["bash"; "-c"; "exit 0"];; Process.run ~is_success:(Int.equal 3) mgr ["sh"; "-c"; "exit 0"];;
Exception: Exception:
Eio.Io Process Child_error Exited (code 0), Eio.Io Process Child_error Exited (code 0),
running command: bash -c "exit 0" running command: sh -c "exit 0"
``` ```
Exit code success can be determined by is_success (Process.parse_out): Exit code success can be determined by is_success (Process.parse_out):

View File

@ -231,3 +231,21 @@ Cancellation:
+Cancel sleeper +Cancel sleeper
Exception: Invalid_argument "No further events scheduled on mock clock". Exception: Invalid_argument "No further events scheduled on mock clock".
``` ```
Sleep:
```ocaml
# try
Eio_mock.Backend.run_full @@ fun env ->
let timeout = Eio.Time.Timeout.seconds env#mono_clock 2. in
Eio.Time.Timeout.sleep timeout;
traceln "Timeout done";
Eio.Time.Timeout.(sleep none);
assert false
with Eio_mock.Backend.Deadlock_detected ->
traceln "Never finished";;
+mock time is now 2
+Timeout done
+Never finished
- : unit = ()
```