1
0
mirror of https://github.com/fafhrd91/actix-net synced 2025-08-13 01:37:05 +02:00

Compare commits

..

151 Commits

Author SHA1 Message Date
Rob Ede
7632f51509 prepare connect v2 stable release (#185) 2020-09-02 22:14:07 +01:00
Rob Ede
d28687d0d7 promote codec/utils out of beta (#184) 2020-08-24 09:18:37 +01:00
Rob Ede
27c6be9881 remove unused type parameter from Framed::replace_codec (#183) 2020-08-20 00:30:26 +01:00
Rob Ede
119dc39f5b prepare codec and utils betas (#182) 2020-08-19 11:00:12 +01:00
Rob Ede
b3010c13e0 solve framed integration with actix-http (#179) 2020-08-18 23:27:37 +01:00
Adrian Wechner
fecdfcd8d4 assert workers greater than zero (#167) 2020-08-18 16:44:22 +01:00
Yuki Okushi
578a560853 connect,tls: Bump up to next alpha versions (#181) 2020-08-17 15:39:17 +01:00
Rob Ede
fb098536ee bump MSRV to 1.42 (#180) 2020-08-17 15:37:57 +01:00
Rob Ede
5d28be9ad6 fix actix-service readme reference (#176) 2020-08-11 12:20:09 +01:00
Rob Ede
a5a6b6704c prepare actix-service 1.0.6 release (#175) 2020-08-09 16:10:58 +01:00
Igor Aleksanov
afb0a3c9fc actix-service: Fix clippy warning in benches (#174) 2020-08-07 17:16:45 +09:00
Miloas
02aaa75591 fix actix-service doc error (#172) 2020-08-06 11:21:51 +01:00
Yuki Okushi
ed4b708c66 Fix CI on MSRV check (#171) 2020-08-05 09:02:41 +09:00
Yuki Okushi
235a76dcd4 GHA: Switch action to the official setup-msys2 (#169) 2020-07-29 08:47:32 +09:00
Matt Kantor
0c5f1da625 Remove garbled doc comment for actix_router::IntoPattern::is_single (#168) 2020-07-29 05:46:53 +09:00
Yuki Okushi
8ace9264b7 Check code style with rustfmt on CI (#164) 2020-07-22 12:32:13 +09:00
Yuki Okushi
0dca1a705a actix-utils: Remove unsound custom Cell as well (#161) 2020-07-22 01:14:32 +01:00
Juan Aguilar
5d6d309e66 Simplify bcodec decode (#162) 2020-07-20 23:09:24 +09:00
Juan Aguilar
8d0bd7ce1c Improve bcodec encode performance (#157) 2020-07-19 22:36:51 +01:00
Sergey "Shnatsel" Davidoff
a67e38b4a0 Remove unsound custom Cell (#158) 2020-07-20 06:05:36 +09:00
Rob Ede
334c98575a Upgrade tokio utils to 0.3 (#138) 2020-07-20 05:44:26 +09:00
Rob Ede
a9b5a7b070 Create PULL_REQUEST_TEMPLATE.md (#159) 2020-07-20 03:01:09 +09:00
Yuki Okushi
61176f6410 Update rustls-related dependencies (#154) 2020-07-14 11:14:06 +01:00
Yuki Okushi
10b4c30a06 Use OR instead of deprecated / in license field (#155) 2020-07-14 11:11:30 +01:00
Yuki Okushi
7f550bcf0f threadpool: Bump up to 0.3.3 (#156) 2020-07-14 11:10:15 +01:00
Yuki Okushi
887f11f787 Merge pull request #153 from actix/tweak-actions
Tweak actions trigger events
2020-07-08 09:04:05 +09:00
Yuki Okushi
e2a6d352b0 Tweak actions trigger events 2020-07-08 08:38:24 +09:00
Yuki Okushi
f6c697a2dd Merge pull request #152 from paolobarbolini/pl-011
Update parking_lot to 0.11
2020-07-04 03:20:08 +09:00
Paolo Barbolini
5ecdfd684a Update parking_lot to 0.11 2020-07-03 17:37:10 +02:00
Yuki Okushi
7140c04c44 Merge pull request #149 from taiki-e/pin-project
Remove uses of pin_project::project attribute
2020-06-07 02:01:08 +09:00
Taiki Endo
9528df4486 Remove uses of pin_project::project attribute
pin-project will deprecate the project attribute due to some unfixable
limitations.

Refs: https://github.com/taiki-e/pin-project/issues/225
2020-06-06 06:42:45 +09:00
Pen Tree
755a8bb9d1 fix codec doc links (#148) 2020-06-02 18:05:39 +01:00
Yuki Okushi
f3cb6efc30 Merge pull request #146 from actix/cache-v2
Update `actions/cache` to v2
2020-05-28 04:59:34 +09:00
Yuki Okushi
87b857705c Update actions/cache to v2 2020-05-28 03:14:01 +09:00
Yuki Okushi
c897c5d3eb Merge pull request #145 from JohnTitor/new-threalpool
threadpool: Bump up to 0.3.2
2020-05-20 15:24:39 +09:00
Yuki Okushi
134e76b8b4 threadpool: Bump up to 0.3.2 2020-05-20 14:19:16 +09:00
Yuki Okushi
f3a401c23b Merge pull request #144 from JohnTitor/codecov-config
Add codecov config
2020-05-20 11:03:31 +09:00
Yuki Okushi
f7e8a912b3 Add codecov config 2020-05-19 14:45:39 +09:00
Yuki Okushi
11a1e11858 Merge pull request #143 from JohnTitor/new-testing
testing: Bump up to 1.0.1
2020-05-19 14:37:54 +09:00
Yuki Okushi
d0b27ee7e6 testing: Bump up to 1.0.1 2020-05-19 14:08:08 +09:00
Yuki Okushi
2d2b0591a2 Merge pull request #142 from JohnTitor/new-server
server: Bump up to 1.0.3
2020-05-19 13:58:39 +09:00
Yuki Okushi
abbc5f715f server: Bump up to 1.0.3 2020-05-19 10:23:17 +09:00
Yuki Okushi
140a6c76e3 Merge pull request #141 from actix/fix-ci
Only check compilation on mingw CI
2020-05-19 09:39:03 +09:00
Yuki Okushi
2395b28c5e Only check compilation on mingw CI
Disabled to run tests since somehow linking with OpenSSL is broken.
2020-05-19 09:11:27 +09:00
Yuki Okushi
aad4812ba6 Merge pull request #140 from JohnTitor/replace-net2
Replace deprecated `net2` crate with `socket2`
2020-05-19 08:58:40 +09:00
Yuki Okushi
ac6c78c476 testing: Replace net2 crate with socket2 2020-05-19 08:21:40 +09:00
Yuki Okushi
8218a098e8 server: Replace net2 crate with socket2 2020-05-19 08:17:44 +09:00
Yuki Okushi
49a6f525be Merge pull request #139 from JohnTitor/next-macros
macros: Bump up to 0.1.2
2020-05-19 07:50:46 +09:00
Yuki Okushi
f59ff82395 macros: Bump up to 0.1.2 2020-05-18 15:36:23 +09:00
Yuki Okushi
f7cc62564d Merge pull request #136 from JohnTitor/connect-alpha-3
actix-connect: Bump up to 2.0.0-alpha.3
2020-05-08 01:36:16 +09:00
Yuki Okushi
b125e2bdce actix-connect: Bump up to 2.0.0-alpha.3 2020-05-08 01:07:57 +09:00
Yuki Okushi
a5c185e80e Merge pull request #135 from actix/fix/unresolverd
correct spelling of ConnectError::Unresolved
2020-05-06 14:45:30 +09:00
Rob Ede
523cee0351 correct spelling of ConnectError::Unresolved 2020-05-03 23:14:22 +01:00
Yuki Okushi
343b3c09fc Merge pull request #134 from JohnTitor/new-rt
Bump up `actix-rt` to 1.1.1
2020-04-30 14:34:17 +09:00
Yuki Okushi
8a10580663 Bump up actix-rt to 1.1.1 2020-04-30 03:07:12 +09:00
Yuki Okushi
1b4a117063 Merge pull request #128 from Jonathas-Conceicao/topic/fix_memory_leak
actix-rt: Spawn future to cleanup pending JoinHandles
2020-04-30 02:58:13 +09:00
Yuki Okushi
700997fe48 Merge pull request #133 from actix/macro-compile-testing
add macro compile tests
2020-04-29 15:33:00 +09:00
Rob Ede
4c5568ed70 add trybuild compile tests 2020-04-26 20:11:16 +01:00
Yuki Okushi
7d0cfe1b4d Merge pull request #131 from danpintara/pull-1
actix-macros: Simplify test macros by using original signature
2020-04-23 02:33:52 +09:00
Daniel Pintara
e35c261c9f actix-macros: test: Simplify by using #sig instead of #name(#inputs) #ret 2020-04-22 00:13:32 +07:00
Yuki Okushi
115ef3fcb3 Merge pull request #130 from JohnTitor/dont-clone
Remove unnecessary clone usage
2020-04-20 08:37:10 +09:00
Yuki Okushi
c0482e2532 Remove unnecessary clone usage 2020-04-20 08:02:08 +09:00
Jonathas-Conceicao
6906f25e01 actix-rt: Set threshold size for arbiter's pending futures list
Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-04-16 03:12:05 -03:00
Jonathas-Conceicao
06bca19524 actix-rt: Spawn future to cleanup pending JoinHandles
Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-04-09 20:36:44 -03:00
Yuki Okushi
e9e2185296 Merge pull request #127 from rubdos/test-fixture-integration
Forward actix_rt::test arguments to test function.
2020-04-09 17:45:17 +09:00
Ruben De Smet
aae52a80ab Forward actix_rt::test arguments to test function.
Previously,

```rust
async fn foo(_a: u32) {}
```

would compile to

```rust
fn foo() {/* something */}
```

This patches changes this behaviour to

```rust
fn foo(_a: u32) {/* something */}
```

by simply forwarding the input arguments.

This allows any test fixture library (e.g. `rstest`, cfr.
https://github.com/la10736/rstest/issues/85) to integrate with
actix::test.
2020-04-08 16:48:10 +02:00
Yuki Okushi
65e2e8052e Release actix-rt 1.1.0 (#126)
* Release actix-rt 1.1.0

* Update actix-rt/CHANGES.md
2020-04-08 16:34:07 +09:00
Jonathas-Conceicao
783880bb0a actix-rt: Add Arbiter::is_running helper and fix System::is_set doc
`Arbiter::is_running` can be used to check if the current even-loop is currently
running; which should also work after the system has stopped. `System::is_set`
was updated to reflect what it actually does, it tells if the event loop has
started, which alone can't tell if it has stopped.

Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-04-05 21:00:54 -03:00
Jonathas-Conceicao
69e8df9d62 actix-rt: Run rustfmt
Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-04-05 21:00:54 -03:00
Yuki Okushi
9addf1a36b Merge pull request #125 from actix/fix/noisy-check
fix noisy check warning
2020-04-05 13:20:25 +09:00
Rob Ede
187a58472d fix noisy check warning 2020-04-04 23:57:52 +01:00
Nikolay Kim
30aa0b7bb6 add serde support to bytestring 2020-03-30 11:54:40 +06:00
Yuki Okushi
e775d08d76 Merge pull request #122 from actix/JohnTitor-patch-1
Upload coverage on PRs
2020-03-18 05:31:59 +09:00
Yuki Okushi
d5f95b54b7 Upload coverage on PRs 2020-03-18 05:03:37 +09:00
Yuki Okushi
904f90abc2 Merge pull request #121 from actix/revert-115-JohnTitor-patch-2
Revert "Disable windows-mingw builder temporarily"
2020-03-16 18:06:42 +09:00
Yuki Okushi
950c73077c Revert "Disable windows-mingw builder temporarily" 2020-03-16 17:31:10 +09:00
Yuki Okushi
732731a9c8 Merge pull request #120 from kornelski/err
std Error for BlockingError
2020-03-14 00:14:42 +09:00
Kornel Lesiński
0dd5a7ce1d std Error for BlockingError
#93
2020-03-13 12:35:20 +00:00
Yuki Okushi
7105091e51 Merge pull request #119 from JohnTitor/futures
Minimize `futures-*` dependencies
2020-03-13 05:12:37 +09:00
Yuki Okushi
08959dfc21 actix-tracing: Minimize futures-util dependencies 2020-03-12 07:13:32 +09:00
Yuki Okushi
2792433ad6 actix-codec: Minimize futures-* dependencies 2020-03-12 07:13:32 +09:00
Yuki Okushi
437a7b05c6 actix-rt: Fix build 2020-03-12 07:13:32 +09:00
Yuki Okushi
3d125c5381 actix-testing: Remove unused deps 2020-03-12 07:13:32 +09:00
Yuki Okushi
fbf7d6ef33 Update examples 2020-03-12 07:13:32 +09:00
Yuki Okushi
e6b6f08369 actix-utils: Minimize futures-* dependencies 2020-03-12 07:13:32 +09:00
Yuki Okushi
4e806b3e3f actix-tls: Minimize futures-* dependencies 2020-03-12 07:13:31 +09:00
Yuki Okushi
f5b07053fc actix-server: Minimize futures-* dependencies 2020-03-12 07:13:31 +09:00
Yuki Okushi
dd3bec83bf actix-ioframe: Minimize futures-* dependencies 2020-03-12 07:13:31 +09:00
Yuki Okushi
f955e49930 actix-connect: Minimize futures-* dependencies 2020-03-12 04:22:38 +09:00
Yuki Okushi
4be11b541b Merge pull request #117 from actix/new-connect
Release actix-http v2.0.0-alpha.2
2020-03-08 15:13:52 +09:00
Yuki Okushi
baba533407 Update actix-http dependency 2020-03-08 14:38:07 +09:00
Yuki Okushi
2bf50826b0 Bump up to 2.0.0-alpha.2 2020-03-08 14:37:33 +09:00
Yuki Okushi
41b2a3b2e2 Merge pull request #116 from Jonathas-Conceicao/topic/upgrade_trust_dns
actix-connect: Upgrade versions of trust-dns
2020-03-08 14:31:07 +09:00
Jonathas-Conceicao
7fdd4a1118 actix-connect: Upgrade versions of trust-dns
- `Address` trait is now required to have static lifetime;
- `start_resolver` and `start_default_resolver` are now `async` and may return
  a `ConnectError`;

Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-03-07 14:52:41 -03:00
Jonathas-Conceicao
cb30f9e86a actix-connect: Run cargo fmt
Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-03-07 14:37:39 -03:00
Yuki Okushi
873f69be51 Merge pull request #115 from actix/JohnTitor-patch-2
Disable windows-mingw builder temporarily
2020-03-06 14:11:50 +09:00
Yuki Okushi
0967061f30 Merge pull request #114 from actix/JohnTitor-patch-1
Unpin quote version
2020-03-06 14:11:28 +09:00
Yuki Okushi
59902cb3a3 Disable windows-mingw builder temporarily 2020-03-06 13:48:55 +09:00
Yuki Okushi
857e50120b Unpin quote version 2020-03-06 13:45:21 +09:00
Yuki Okushi
36a2edf1cd Merge pull request #111 from dunnock/master
Fix build with failing quote
2020-03-05 23:05:19 +09:00
Maksym Vorobiov
346bd072d3 fix build with failing quote 2020-03-05 14:58:44 +02:00
Yuki Okushi
8d3d58b3b7 Merge pull request #110 from Aaron1011/fix/better-pin
Replace calls to `Pin::new_unchecked` with `pin_project`.
2020-03-05 21:52:55 +09:00
Aaron Hill
c41b5d8dd4 Replace calls to Pin::new_unchecked with pin_project.
This is a breaking change, as it changes some public methods to take
`Pin<&mut Self>` rather than `&mut self`.

This brings these methods into line with `Stream::poll_next`, which also
takes a `Pin<&mut Self>`
2020-03-04 12:08:52 -05:00
Yuki Okushi
693d5132a9 Merge pull request #109 from JohnTitor/new-tls
actix-tls: Bump up to 2.0.0-alpha.1
2020-03-03 22:29:08 +09:00
Yuki Okushi
f7dac3feb4 Bump up to 2.0.0-alpha.1 2020-03-03 19:47:40 +09:00
Yuki Okushi
ebc11d03f2 Merge pull request #108 from JohnTitor/new-connect
Release `actix-connect` v2.0.0-alpha.1
2020-03-03 18:33:08 +09:00
Yuki Okushi
e3ad5de270 Update actix-connect dependency 2020-03-03 17:24:41 +09:00
Yuki Okushi
91118bb2ce Bump up to 2.0.0-alpha.1 2020-03-03 17:24:25 +09:00
Yuki Okushi
6628688bcf Merge pull request #107 from JohnTitor/rustls-017
Update `rustls` and `tokio-rustls`
2020-03-01 23:48:13 +09:00
Yuki Okushi
b9567359fd actix-tls: Update rustls and tokio-rustls 2020-03-01 12:08:14 +09:00
Yuki Okushi
7dbc0264b1 actix-connect: Update rustls and tokio-rustls 2020-03-01 12:08:14 +09:00
Erich Gubler
1b7c969f6a actix-rt: minimize futures dependencies to futures-{channel,util} with default features off (#104)
* build(deps): minimize `futures` deps by using `futures-channel` and `futures-util` directly

* style(actix-rt): enforce spaces around equals in `Cargo.toml`
2020-02-27 01:15:21 +09:00
Jonathas-Conceicao
f1685d8253 Add Arbiter::local_join associated function
Arbiter::local_join function can be used to await for futures spawned
on current arbiter.

Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-02-26 12:59:46 -03:00
Jonathas-Conceicao
e3b6a33b97 Add integration tests
These initial tests validade basic usage with timed futures for:
- `System::block_on`;
- `Arbiter::new`;
- `Arbiter::stop`;
- `Arbiter::join`;

Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-02-26 12:59:46 -03:00
Yuki Okushi
13b503435f Merge pull request #106 from JohnTitor/server-102
Release actix-server 1.0.2
2020-02-26 20:53:00 +09:00
Yuki Okushi
98f0290f65 actix-server: Bump up to 1.0.2 2020-02-26 19:48:52 +09:00
Yuki Okushi
b8f66f5e7f Update changelog 2020-02-26 19:48:41 +09:00
Yuki Okushi
dd59ee498e Add FIXME comment 2020-02-26 19:48:27 +09:00
Dany Laporte
83320efa31 Avoid error by register() on Windows (#103) 2020-02-26 18:40:31 +09:00
Yuki Okushi
c69bc11e3e Merge pull request #105 from actix/bench
Add action to check benchmark
2020-02-26 17:33:37 +09:00
Yuki Okushi
aad5c42ad7 Add action to check benchmark 2020-02-26 17:11:46 +09:00
Maxim Vorobjov
4d37858fc6 Benchmarks for actix-service: focused around UnsafeCell usage (#98)
* add benchmark comparing unsafecell vs refcell

* fix syntax

* add benches for and_then implementation options

* repeat benches to stabilize
2020-02-26 16:45:23 +09:00
Yuki Okushi
d402f08bb5 Merge pull request #102 from JohnTitor/single-import
Remove single import
2020-02-25 19:11:04 +09:00
Yuki Okushi
fa25e30427 Remove single import 2020-02-25 18:41:15 +09:00
Bo Yao
602db1779e Expose is_set (#99)
* Expose is_set

* Update doc and changes.md
2020-02-25 02:55:02 -03:00
Yuki Okushi
4f2910c6b3 Merge pull request #96 from actix/JohnTitor-patch-1
Disable coverage for PRs
2020-02-15 01:55:20 +09:00
Yuki Okushi
9f7d6bc068 Disable coverage for PRs 2020-02-14 07:30:21 +09:00
Yuki Okushi
6908b58943 Merge pull request #92 from actix/bye-travis
Move script from Travis to Actions
2020-02-02 06:28:42 +09:00
Yuki Okushi
043057ecbd Fix import scopes 2020-02-01 23:32:08 +09:00
Yuki Okushi
e12bf9200b Clean up metadata 2020-01-31 02:21:25 +09:00
Yuki Okushi
03d431e663 Add badges on README 2020-01-31 00:01:47 +09:00
Yuki Okushi
f0d352604e Remove travis config 2020-01-31 00:01:34 +09:00
Yuki Okushi
2f67e4f563 Use markdown format 2020-01-31 00:01:24 +09:00
Yuki Okushi
d1155d60ec Tweak Actions 2020-01-31 00:01:11 +09:00
Yuki Okushi
28d9c6a760 Merge pull request #90 from actix/fix-ci
Tweak GitHub Actions
2020-01-30 00:46:21 +09:00
Yuki Okushi
a970c2c997 Remove AppVeyor config 2020-01-29 12:05:55 +09:00
Yuki Okushi
d5a6c83207 Suppress/fix clippy warnings 2020-01-29 12:05:55 +09:00
Yuki Okushi
ee0db9a617 Tweak GitHub Actions 2020-01-29 12:05:55 +09:00
zero-systems
e5b5df1261 Optimize vector fill in builder. (#89)
* optimize vector fill
2020-01-22 06:35:22 +09:00
Nikolay Kim
dbfa13d6be Fixed unsoundness in .and_then()/.then() service combinators 2020-01-16 16:58:11 -08:00
Nikolay Kim
e7c2439543 prep release 2020-01-15 13:35:07 -08:00
Nikolay Kim
3116db5168 revert 1.0.3 changes 2020-01-15 13:24:38 -08:00
Nikolay Kim
5940731ef0 Fix actix-service 1.0.3 compatibility 2020-01-15 11:58:06 -08:00
Rajasekharan Vengalil
aed5fecc8a Add support for tokio tracing for actix Service. (#86)
* Add support for tokio tracing for actix Service.

* Address comments

* Change trace's return type to ApplyTransform

* Remove redundant type args

* Remove reference to MakeSpan from docs
2020-01-15 11:43:52 -08:00
Nikolay Kim
a751899aad Fixed unsoundness in AndThenService impl #83 2020-01-15 11:40:15 -08:00
Nikolay Kim
fa800aeba3 Fix AsRef<str> impl 2020-01-14 15:06:02 -08:00
Nikolay Kim
2f89483635 Merge branch 'master' of github.com:actix/actix-net 2020-01-14 00:42:29 -08:00
Nikolay Kim
3048073919 Add PartialEq<T: AsRef<str>>, AsRef<[u8]> impls 2020-01-13 11:58:31 +06:00
amosonn
4bbba803c1 Fix Service documentation (#85) 2020-01-12 07:44:01 +09:00
Sven-Hendrik Haase
4dcdeb6795 Merge pull request #84 from currency-engineering/master
Minor grammatical fix to docs.
2020-01-10 15:28:19 +01:00
Eric Findlay
3b4f222242 Minor grammatical fix to docs. 2020-01-10 20:52:49 +09:00
126 changed files with 2696 additions and 2220 deletions

View File

@@ -1,41 +0,0 @@
environment:
global:
PROJECT_NAME: actix-net
matrix:
# Stable channel
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
# Nightly channel
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
install:
- ps: >-
If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
$Env:PATH += ';C:\msys64\mingw64\bin'
} ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
$Env:PATH += ';C:\MinGW\bin'
}
- curl -sSf -o rustup-init.exe https://win.rustup.rs
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -Vv
- cargo -V
# 'cargo test' takes care of building for us, so disable Appveyor's build stage.
build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo clean
- cargo test

24
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,24 @@
## PR Type
<!-- What kind of change does this PR make? -->
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
INSERT_PR_TYPE
## PR Checklist
Check your PR fulfills the following:
<!-- For draft PRs check the boxes as you complete them. -->
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] A changelog entry has been made for the appropriate packages.
- [ ] Format code with the latest stable rustfmt
## Overview
<!-- Describe the current and new behavior. -->
<!-- Emphasize any breaking changes. -->
<!-- If this PR fixes or closes an issue, reference it here. -->
<!-- Closes #000 -->

29
.github/workflows/bench.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Benchmark (Linux)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
check_benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
- name: Check benchmark
uses: actions-rs/cargo@v1
with:
command: bench
args: --package=actix-service

34
.github/workflows/clippy-fmt.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
on:
pull_request:
types: [opened, synchronize, reopened]
name: Clippy and rustfmt Check
jobs:
clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
profile: minimal
override: true
- name: Check with rustfmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: clippy
profile: minimal
override: true
- name: Check with Clippy
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features --all --tests

82
.github/workflows/linux.yml vendored Normal file
View File

@@ -0,0 +1,82 @@
name: CI (Linux)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- 1.42.0
- stable
- nightly
name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cache cargo dirs
uses: actions/cache@v2
with:
path:
~/.cargo/registry
~/.cargo/git
~/.cargo/bin
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v2
with:
path: target
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture
- name: Generate coverage file
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
run: |
cargo install cargo-tarpaulin
cargo tarpaulin --out Xml --workspace --all-features
- name: Upload to Codecov
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
uses: codecov/codecov-action@v1
with:
file: cobertura.xml
- name: Clear the cargo caches
run: |
rustup update stable
rustup override set stable
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

43
.github/workflows/macos.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: CI (macOS)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-apple-darwin
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-apple-darwin
profile: minimal
override: true
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture

View File

@@ -1,78 +0,0 @@
name: CI
on: [push, pull_request]
env:
VCPKGRS_DYNAMIC: 1
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
toolchain:
- x86_64-pc-windows-msvc
- x86_64-pc-windows-gnu
- i686-pc-windows-msvc
- x86_64-apple-darwin
version:
- stable
- nightly
include:
- toolchain: x86_64-pc-windows-msvc
os: windows-latest
arch: x64
- toolchain: x86_64-pc-windows-gnu
os: windows-latest
- toolchain: i686-pc-windows-msvc
os: windows-latest
arch: x86
- toolchain: x86_64-apple-darwin
os: macOS-latest
name: ${{ matrix.version }} - ${{ matrix.toolchain }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@master
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-${{ matrix.toolchain }}
default: true
- name: Install OpenSSL
if: matrix.toolchain == 'x86_64-pc-windows-msvc' || matrix.toolchain == 'i686-pc-windows-msvc'
run: |
vcpkg integrate install
vcpkg install openssl:${{ matrix.arch }}-windows
- name: check nightly
if: matrix.version == 'nightly'
uses: actions-rs/cargo@v1
with:
command: check
args: --all --benches --bins --examples --tests
- name: check stable
if: matrix.version == 'stable'
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
if: matrix.toolchain != 'x86_64-pc-windows-gnu'
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features -- --nocapture
- name: tests on x86_64-pc-windows-gnu
if: matrix.toolchain == 'x86_64-pc-windows-gnu'
uses: actions-rs/cargo@v1
with:
command: test
args: --all -- --nocapture

45
.github/workflows/windows-mingw.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: CI (Windows-mingw)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-pc-windows-gnu
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-pc-windows-gnu
profile: minimal
override: true
- name: Install MSYS2
uses: msys2/setup-msys2@v2
- name: Install packages
run: |
msys2 -c 'pacman -Sy --noconfirm pacman'
msys2 -c 'pacman --noconfirm -S base-devel pkg-config'
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests

69
.github/workflows/windows.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: CI (Windows)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
env:
VCPKGRS_DYNAMIC: 1
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
target:
- x86_64-pc-windows-msvc
- i686-pc-windows-msvc
name: ${{ matrix.version }} - ${{ matrix.target }}
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-${{ matrix.target }}
profile: minimal
override: true
- name: Install OpenSSL (x64)
if: matrix.target == 'x86_64-pc-windows-msvc'
run: |
vcpkg integrate install
vcpkg install openssl:x64-windows
Get-ChildItem C:\vcpkg\installed\x64-windows\bin
Get-ChildItem C:\vcpkg\installed\x64-windows\lib
Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll
Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll
- name: Install OpenSSL (x86)
if: matrix.target == 'i686-pc-windows-msvc'
run: |
vcpkg integrate install
vcpkg install openssl:x86-windows
Get-ChildItem C:\vcpkg\installed\x86-windows\bin
Get-ChildItem C:\vcpkg\installed\x86-windows\lib
Copy-Item C:\vcpkg\installed\x86-windows\bin\libcrypto-1_1.dll C:\vcpkg\installed\x86-windows\bin\libcrypto.dll
Copy-Item C:\vcpkg\installed\x86-windows\bin\libssl-1_1.dll C:\vcpkg\installed\x86-windows\bin\libssl.dll
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture

View File

@@ -1,49 +0,0 @@
language: rust
sudo: required
dist: trusty
cache:
cargo: true
apt: true
matrix:
include:
- rust: stable
- rust: beta
- rust: nightly-2019-11-07
allow_failures:
- rust: nightly-2019-11-07
env:
global:
- RUSTFLAGS="-C link-dead-code"
- OPENSSL_VERSION=openssl-1.0.2
before_install:
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
- sudo apt-get update -qq
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
before_cache: |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-07" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --version 0.6.11 cargo-tarpaulin
fi
# Add clippy
before_script:
- export PATH=$PATH:~/.cargo/bin
script:
- |
if [[ "$TRAVIS_RUST_VERSION" != "nightly-2019-11-07" ]]; then
cargo clean
cargo test --all --all-features -- --nocapture
fi
after_success:
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-07" ]]; then
taskset -c 0 cargo tarpaulin --all --all-features --out Xml
echo "Uploaded code coverage"
bash <(curl -s https://codecov.io/bash)
fi

View File

@@ -2,7 +2,6 @@
members = [
"actix-codec",
"actix-connect",
"actix-ioframe",
"actix-rt",
"actix-macros",
"actix-service",
@@ -10,6 +9,7 @@ members = [
"actix-testing",
"actix-threadpool",
"actix-tls",
"actix-tracing",
"actix-utils",
"router",
"string",
@@ -18,7 +18,6 @@ members = [
[patch.crates-io]
actix-codec = { path = "actix-codec" }
actix-connect = { path = "actix-connect" }
actix-ioframe = { path = "actix-ioframe" }
actix-rt = { path = "actix-rt" }
actix-macros = { path = "actix-macros" }
actix-server = { path = "actix-server" }
@@ -26,6 +25,7 @@ actix-service = { path = "actix-service" }
actix-testing = { path = "actix-testing" }
actix-threadpool = { path = "actix-threadpool" }
actix-tls = { path = "actix-tls" }
actix-tracing = { path = "actix-tracing" }
actix-utils = { path = "actix-utils" }
actix-router = { path = "router" }
bytestring = { path = "string" }

View File

@@ -1,11 +1,20 @@
# Actix net [![Build Status](https://travis-ci.org/actix/actix-net.svg?branch=master)](https://travis-ci.org/actix/actix-net) [![codecov](https://codecov.io/gh/actix/actix-net/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-net) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# Actix net [![codecov](https://codecov.io/gh/actix/actix-net/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-net) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Actix net - framework for composable network services
## Build statuses
| Platform | Build Status |
| ---------------- | ------------ |
| Linux | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Linux)") |
| macOS | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28macOS%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(macOS)") |
| Windows | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows)") |
| Windows (MinGW) | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows-mingw%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows-mingw)") |
## Documentation & community resources
* [Chat on gitter](https://gitter.im/actix/actix)
* Minimum supported Rust version: 1.39 or later
* [Chat on Gitter](https://gitter.im/actix/actix)
* Minimum supported Rust version: 1.42 or later
## Example

View File

@@ -1,6 +1,21 @@
# Changes
* Use `.advance()` intead of `.split_to()`
## Unreleased - 2020-xx-xx
## 0.3.0 - 2020-08-23
* No changes from beta 2.
## 0.3.0-beta.2 - 2020-08-19
* Remove unused type parameter from `Framed::replace_codec`.
## 0.3.0-beta.1 - 2020-08-19
* Use `.advance()` instead of `.split_to()`.
* Upgrade `tokio-util` to `0.3`.
* Improve `BytesCodec` `.encode()` performance
* Simplify `BytesCodec` `.decode()`
* Rename methods on `Framed` to better describe their use.
* Add method on `Framed` to get a pinned reference to the underlying I/O.
* Add method on `Framed` check emptiness of read buffer.
## [0.2.0] - 2019-12-10
@@ -8,7 +23,7 @@
## [0.2.0-alpha.4]
* Fix buffer remaining capacity calcualtion
* Fix buffer remaining capacity calculation
## [0.2.0-alpha.3]

View File

@@ -1,16 +1,15 @@
[package]
name = "actix-codec"
version = "0.2.0"
version = "0.3.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Utilities for encoding and decoding frames"
description = "Codec utilities for working with framed protocols."
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-codec/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
edition = "2018"
workspace = ".."
[lib]
name = "actix_codec"
@@ -19,8 +18,9 @@ path = "src/lib.rs"
[dependencies]
bitflags = "1.2.1"
bytes = "0.5.2"
futures-core = "0.3.1"
futures-sink = "0.3.1"
tokio = { version = "0.2.4", default-features=false }
tokio-util = { version = "0.2.0", default-features=false, features=["codec"] }
log = "0.4"
futures-core = { version = "0.3.4", default-features = false }
futures-sink = { version = "0.3.4", default-features = false }
log = "0.4"
pin-project = "0.4.17"
tokio = { version = "0.2.5", default-features = false }
tokio-util = { version = "0.3.1", default-features = false, features = ["codec"] }

View File

@@ -1,4 +1,4 @@
use bytes::{BufMut, Bytes, BytesMut};
use bytes::{Buf, Bytes, BytesMut};
use std::io;
use super::{Decoder, Encoder};
@@ -9,13 +9,12 @@ use super::{Decoder, Encoder};
#[derive(Debug, Copy, Clone)]
pub struct BytesCodec;
impl Encoder for BytesCodec {
type Item = Bytes;
impl Encoder<Bytes> for BytesCodec {
type Error = io::Error;
#[inline]
fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
dst.reserve(item.len());
dst.put(item);
dst.extend_from_slice(item.bytes());
Ok(())
}
}
@@ -28,8 +27,7 @@ impl Decoder for BytesCodec {
if src.is_empty() {
Ok(None)
} else {
let len = src.len();
Ok(Some(src.split_to(len)))
Ok(Some(src.split()))
}
}
}

View File

@@ -5,10 +5,13 @@ use std::{fmt, io};
use bytes::{Buf, BytesMut};
use futures_core::{ready, Stream};
use futures_sink::Sink;
use pin_project::pin_project;
use crate::{AsyncRead, AsyncWrite, Decoder, Encoder};
/// Low-water mark
const LW: usize = 1024;
/// High-water mark
const HW: usize = 8 * 1024;
bitflags::bitflags! {
@@ -20,7 +23,15 @@ bitflags::bitflags! {
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using
/// the `Encoder` and `Decoder` traits to encode and decode frames.
///
/// Raw I/O objects work with byte sequences, but higher-level code usually
/// wants to batch these into meaningful chunks, called "frames". This
/// method layers framing on top of an I/O object, by using the `Encoder`/`Decoder`
/// traits to handle encoding and decoding of message frames. Note that
/// the incoming and outgoing frame types may be distinct.
#[pin_project]
pub struct Framed<T, U> {
#[pin]
io: T,
codec: U,
flags: Flags,
@@ -28,22 +39,11 @@ pub struct Framed<T, U> {
write_buf: BytesMut,
}
impl<T, U> Unpin for Framed<T, U> {}
impl<T, U> Framed<T, U>
where
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder,
U: Decoder,
{
/// Provides a `Stream` and `Sink` interface for reading and writing to this
/// `Io` object, using `Decode` and `Encode` to read and write the raw data.
///
/// Raw I/O objects work with byte sequences, but higher-level code usually
/// wants to batch these into meaningful chunks, called "frames". This
/// method layers framing on top of an I/O object, by using the `Codec`
/// traits to handle encoding and decoding of messages frames. Note that
/// the incoming and outgoing frame types may be distinct.
///
/// This function returns a *single* object that is both `Stream` and
/// `Sink`; grouping this into a single object is often useful for layering
/// things like gzip or TLS, which require both read and write access to the
@@ -60,40 +60,13 @@ where
}
impl<T, U> Framed<T, U> {
/// Provides a `Stream` and `Sink` interface for reading and writing to this
/// `Io` object, using `Decode` and `Encode` to read and write the raw data.
///
/// Raw I/O objects work with byte sequences, but higher-level code usually
/// wants to batch these into meaningful chunks, called "frames". This
/// method layers framing on top of an I/O object, by using the `Codec`
/// traits to handle encoding and decoding of messages frames. Note that
/// the incoming and outgoing frame types may be distinct.
///
/// This function returns a *single* object that is both `Stream` and
/// `Sink`; grouping this into a single object is often useful for layering
/// things like gzip or TLS, which require both read and write access to the
/// underlying object.
///
/// This objects takes a stream and a readbuffer and a writebuffer. These
/// field can be obtained from an existing `Framed` with the
/// `into_parts` method.
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
Framed {
io: parts.io,
codec: parts.codec,
flags: parts.flags,
write_buf: parts.write_buf,
read_buf: parts.read_buf,
}
}
/// Returns a reference to the underlying codec.
pub fn get_codec(&self) -> &U {
pub fn codec_ref(&self) -> &U {
&self.codec
}
/// Returns a mutable reference to the underlying codec.
pub fn get_codec_mut(&mut self) -> &mut U {
pub fn codec_mut(&mut self) -> &mut U {
&mut self.codec
}
@@ -103,20 +76,29 @@ impl<T, U> Framed<T, U> {
/// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with.
pub fn get_ref(&self) -> &T {
pub fn io_ref(&self) -> &T {
&self.io
}
/// Returns a mutable reference to the underlying I/O stream wrapped by
/// `Frame`.
/// Returns a mutable reference to the underlying I/O stream.
///
/// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with.
pub fn get_mut(&mut self) -> &mut T {
pub fn io_mut(&mut self) -> &mut T {
&mut self.io
}
/// Returns a `Pin` of a mutable reference to the underlying I/O stream.
pub fn io_pin(self: Pin<&mut Self>) -> Pin<&mut T> {
self.project().io
}
/// Check if read buffer is empty.
pub fn is_read_buf_empty(&self) -> bool {
self.read_buf.is_empty()
}
/// Check if write buffer is empty.
pub fn is_write_buf_empty(&self) -> bool {
self.write_buf.is_empty()
@@ -127,8 +109,15 @@ impl<T, U> Framed<T, U> {
self.write_buf.len() >= HW
}
/// Check if framed is able to write more data.
///
/// `Framed` object considers ready if there is free space in write buffer.
pub fn is_write_ready(&self) -> bool {
self.write_buf.len() < HW
}
/// Consume the `Frame`, returning `Frame` with different codec.
pub fn into_framed<U2>(self, codec: U2) -> Framed<T, U2> {
pub fn replace_codec<U2>(self, codec: U2) -> Framed<T, U2> {
Framed {
codec,
io: self.io,
@@ -139,7 +128,7 @@ impl<T, U> Framed<T, U> {
}
/// Consume the `Frame`, returning `Frame` with different io.
pub fn map_io<F, T2>(self, f: F) -> Framed<T2, U>
pub fn into_map_io<F, T2>(self, f: F) -> Framed<T2, U>
where
F: Fn(T) -> T2,
{
@@ -153,7 +142,7 @@ impl<T, U> Framed<T, U> {
}
/// Consume the `Frame`, returning `Frame` with different codec.
pub fn map_codec<F, U2>(self, f: F) -> Framed<T, U2>
pub fn into_map_codec<F, U2>(self, f: F) -> Framed<T, U2>
where
F: Fn(U) -> U2,
{
@@ -165,6 +154,208 @@ impl<T, U> Framed<T, U> {
write_buf: self.write_buf,
}
}
}
impl<T, U> Framed<T, U> {
/// Serialize item and Write to the inner buffer
pub fn write<I>(mut self: Pin<&mut Self>, item: I) -> Result<(), <U as Encoder<I>>::Error>
where
T: AsyncWrite,
U: Encoder<I>,
{
let this = self.as_mut().project();
let remaining = this.write_buf.capacity() - this.write_buf.len();
if remaining < LW {
this.write_buf.reserve(HW - remaining);
}
this.codec.encode(item, this.write_buf)?;
Ok(())
}
/// Try to read underlying I/O stream and decode item.
pub fn next_item(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<<U as Decoder>::Item, U::Error>>>
where
T: AsyncRead,
U: Decoder,
{
loop {
let mut this = self.as_mut().project();
// Repeatedly call `decode` or `decode_eof` as long as it is
// "readable". Readable is defined as not having returned `None`. If
// the upstream has returned EOF, and the decoder is no longer
// readable, it can be assumed that the decoder will never become
// readable again, at which point the stream is terminated.
if this.flags.contains(Flags::READABLE) {
if this.flags.contains(Flags::EOF) {
match this.codec.decode_eof(&mut this.read_buf) {
Ok(Some(frame)) => return Poll::Ready(Some(Ok(frame))),
Ok(None) => return Poll::Ready(None),
Err(e) => return Poll::Ready(Some(Err(e))),
}
}
log::trace!("attempting to decode a frame");
match this.codec.decode(&mut this.read_buf) {
Ok(Some(frame)) => {
log::trace!("frame decoded from buffer");
return Poll::Ready(Some(Ok(frame)));
}
Err(e) => return Poll::Ready(Some(Err(e))),
_ => (), // Need more data
}
this.flags.remove(Flags::READABLE);
}
debug_assert!(!this.flags.contains(Flags::EOF));
// Otherwise, try to read more data and try again. Make sure we've got room
let remaining = this.read_buf.capacity() - this.read_buf.len();
if remaining < LW {
this.read_buf.reserve(HW - remaining)
}
let cnt = match this.io.poll_read_buf(cx, &mut this.read_buf) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))),
Poll::Ready(Ok(cnt)) => cnt,
};
if cnt == 0 {
this.flags.insert(Flags::EOF);
}
this.flags.insert(Flags::READABLE);
}
}
/// Flush write buffer to underlying I/O stream.
pub fn flush<I>(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), U::Error>>
where
T: AsyncWrite,
U: Encoder<I>,
{
let mut this = self.as_mut().project();
log::trace!("flushing framed transport");
while !this.write_buf.is_empty() {
log::trace!("writing; remaining={}", this.write_buf.len());
let n = ready!(this.io.as_mut().poll_write(cx, this.write_buf))?;
if n == 0 {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to write frame to transport",
)
.into()));
}
// remove written data
this.write_buf.advance(n);
}
// Try flushing the underlying IO
ready!(this.io.poll_flush(cx))?;
log::trace!("framed transport flushed");
Poll::Ready(Ok(()))
}
/// Flush write buffer and shutdown underlying I/O stream.
pub fn close<I>(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), U::Error>>
where
T: AsyncWrite,
U: Encoder<I>,
{
let mut this = self.as_mut().project();
ready!(this.io.as_mut().poll_flush(cx))?;
ready!(this.io.as_mut().poll_shutdown(cx))?;
Poll::Ready(Ok(()))
}
}
impl<T, U> Stream for Framed<T, U>
where
T: AsyncRead,
U: Decoder,
{
type Item = Result<U::Item, U::Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.next_item(cx)
}
}
impl<T, U, I> Sink<I> for Framed<T, U>
where
T: AsyncWrite,
U: Encoder<I>,
U::Error: From<io::Error>,
{
type Error = U::Error;
fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.is_write_ready() {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
self.write(item)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.flush(cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.close(cx)
}
}
impl<T, U> fmt::Debug for Framed<T, U>
where
T: fmt::Debug,
U: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Framed")
.field("io", &self.io)
.field("codec", &self.codec)
.finish()
}
}
impl<T, U> Framed<T, U> {
/// This function returns a *single* object that is both `Stream` and
/// `Sink`; grouping this into a single object is often useful for layering
/// things like gzip or TLS, which require both read and write access to the
/// underlying object.
///
/// These objects take a stream, a read buffer and a write buffer. These
/// fields can be obtained from an existing `Framed` with the `into_parts` method.
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
Framed {
io: parts.io,
codec: parts.codec,
flags: parts.flags,
write_buf: parts.write_buf,
read_buf: parts.read_buf,
}
}
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer
/// with unprocessed data, and the codec.
@@ -183,198 +374,6 @@ impl<T, U> Framed<T, U> {
}
}
impl<T, U> Framed<T, U> {
/// Serialize item and Write to the inner buffer
pub fn write(&mut self, item: <U as Encoder>::Item) -> Result<(), <U as Encoder>::Error>
where
T: AsyncWrite,
U: Encoder,
{
let remaining = self.write_buf.capacity() - self.write_buf.len();
if remaining < LW {
self.write_buf.reserve(HW - remaining);
}
self.codec.encode(item, &mut self.write_buf)?;
Ok(())
}
/// Check if framed is able to write more data.
///
/// `Framed` object considers ready if there is free space in write buffer.
pub fn is_write_ready(&self) -> bool {
self.write_buf.len() < HW
}
/// Try to read underlying I/O stream and decode item.
pub fn next_item(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<U::Item, U::Error>>>
where
T: AsyncRead,
U: Decoder,
{
loop {
// Repeatedly call `decode` or `decode_eof` as long as it is
// "readable". Readable is defined as not having returned `None`. If
// the upstream has returned EOF, and the decoder is no longer
// readable, it can be assumed that the decoder will never become
// readable again, at which point the stream is terminated.
if self.flags.contains(Flags::READABLE) {
if self.flags.contains(Flags::EOF) {
match self.codec.decode_eof(&mut self.read_buf) {
Ok(Some(frame)) => return Poll::Ready(Some(Ok(frame))),
Ok(None) => return Poll::Ready(None),
Err(e) => return Poll::Ready(Some(Err(e))),
}
}
log::trace!("attempting to decode a frame");
match self.codec.decode(&mut self.read_buf) {
Ok(Some(frame)) => {
log::trace!("frame decoded from buffer");
return Poll::Ready(Some(Ok(frame)));
}
Err(e) => return Poll::Ready(Some(Err(e))),
_ => (), // Need more data
}
self.flags.remove(Flags::READABLE);
}
debug_assert!(!self.flags.contains(Flags::EOF));
// Otherwise, try to read more data and try again. Make sure we've got room
let remaining = self.read_buf.capacity() - self.read_buf.len();
if remaining < LW {
self.read_buf.reserve(HW - remaining)
}
let cnt = match unsafe {
Pin::new_unchecked(&mut self.io).poll_read_buf(cx, &mut self.read_buf)
} {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))),
Poll::Ready(Ok(cnt)) => cnt,
};
if cnt == 0 {
self.flags.insert(Flags::EOF);
}
self.flags.insert(Flags::READABLE);
}
}
/// Flush write buffer to underlying I/O stream.
pub fn flush(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), U::Error>>
where
T: AsyncWrite,
U: Encoder,
{
log::trace!("flushing framed transport");
while !self.write_buf.is_empty() {
log::trace!("writing; remaining={}", self.write_buf.len());
let n = ready!(unsafe {
Pin::new_unchecked(&mut self.io).poll_write(cx, &self.write_buf)
})?;
if n == 0 {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to write frame to transport",
)
.into()));
}
// remove written data
self.write_buf.advance(n);
}
// Try flushing the underlying IO
ready!(unsafe { Pin::new_unchecked(&mut self.io).poll_flush(cx) })?;
log::trace!("framed transport flushed");
Poll::Ready(Ok(()))
}
/// Flush write buffer and shutdown underlying I/O stream.
pub fn close(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), U::Error>>
where
T: AsyncWrite,
U: Encoder,
{
unsafe {
ready!(Pin::new_unchecked(&mut self.io).poll_flush(cx))?;
ready!(Pin::new_unchecked(&mut self.io).poll_shutdown(cx))?;
}
Poll::Ready(Ok(()))
}
}
impl<T, U> Stream for Framed<T, U>
where
T: AsyncRead,
U: Decoder,
{
type Item = Result<U::Item, U::Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.next_item(cx)
}
}
impl<T, U> Sink<U::Item> for Framed<T, U>
where
T: AsyncWrite,
U: Encoder,
U::Error: From<io::Error>,
{
type Error = U::Error;
fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.is_write_ready() {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
fn start_send(
mut self: Pin<&mut Self>,
item: <U as Encoder>::Item,
) -> Result<(), Self::Error> {
self.write(item)
}
fn poll_flush(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
self.flush(cx)
}
fn poll_close(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
self.close(cx)
}
}
impl<T, U> fmt::Debug for Framed<T, U>
where
T: fmt::Debug,
U: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Framed")
.field("io", &self.io)
.field("codec", &self.codec)
.finish()
}
}
/// `FramedParts` contains an export of the data of a Framed transport.
/// It can be used to construct a new `Framed` with a different codec.
/// It contains all current buffers and the inner transport.

View File

@@ -2,11 +2,15 @@
//!
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
//! Framed streams are also known as [transports].
//! Framed streams are also known as `transports`.
//!
//! [`AsyncRead`]: #
//! [`AsyncWrite`]: #
#![deny(rust_2018_idioms, warnings)]
//! [`AsyncRead`]: AsyncRead
//! [`AsyncWrite`]: AsyncWrite
//! [`Sink`]: futures_sink::Sink
//! [`Stream`]: futures_core::Stream
#![deny(rust_2018_idioms)]
#![warn(missing_docs)]
mod bcodec;
mod framed;

View File

@@ -1,5 +1,44 @@
# Changes
## Unreleased
## 2.0.0-alpha.4 - 2020-08-17
### Changed
* Update `rustls` dependency to 0.18
* Update `tokio-rustls` dependency to 0.14
## [2.0.0-alpha.3] - 2020-05-08
### Fixed
* Corrected spelling of `ConnectError::Unresolverd` to `ConnectError::Unresolved`
## [2.0.0-alpha.2] - 2020-03-08
### Changed
* Update `trust-dns-proto` dependency to 0.19. [#116]
* Update `trust-dns-resolver` dependency to 0.19. [#116]
* `Address` trait is now required to have static lifetime. [#116]
* `start_resolver` and `start_default_resolver` are now `async` and may return a `ConnectError`. [#116]
[#116]: https://github.com/actix/actix-net/pull/116
## [2.0.0-alpha.1] - 2020-03-03
### Changed
* Update `rustls` dependency to 0.17
* Update `tokio-rustls` dependency to 0.13
## [1.0.2] - 2020-01-15
* Fix actix-service 1.0.3 compatibility
## [1.0.1] - 2019-12-15
* Fix trust-dns-resolver compilation

View File

@@ -1,16 +1,15 @@
[package]
name = "actix-connect"
version = "1.0.1"
version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix connect - tcp connector service"
description = "TCP connector service for Actix ecosystem."
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-connect/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
edition = "2018"
workspace = ".."
[package.metadata.docs.rs]
features = ["openssl", "rustls", "uri"]
@@ -32,27 +31,28 @@ rustls = ["rust-tls", "tokio-rustls", "webpki"]
uri = ["http"]
[dependencies]
actix-service = "1.0.0"
actix-codec = "0.2.0"
actix-utils = "1.0.3"
actix-rt = "1.0.0"
actix-service = "1.0.6"
actix-codec = "0.3.0"
actix-utils = "2.0.0"
actix-rt = "1.1.1"
derive_more = "0.99.2"
either = "1.5.2"
futures = "0.3.1"
either = "1.5.3"
futures-util = { version = "0.3.4", default-features = false }
http = { version = "0.2.0", optional = true }
log = "0.4"
trust-dns-proto = "=0.18.0-alpha.2"
trust-dns-resolver = "=0.18.0-alpha.2"
trust-dns-proto = { version = "0.19", default-features = false, features = ["tokio-runtime"] }
trust-dns-resolver = { version = "0.19", default-features = false, features = ["tokio-runtime", "system-config"] }
# openssl
open-ssl = { version="0.10", package = "openssl", optional = true }
open-ssl = { package = "openssl", version = "0.10", optional = true }
tokio-openssl = { version = "0.4.0", optional = true }
# rustls
rust-tls = { version = "0.16.0", package = "rustls", optional = true }
tokio-rustls = { version = "0.12.0", optional = true }
rust-tls = { package = "rustls", version = "0.18.0", optional = true }
tokio-rustls = { version = "0.14.0", optional = true }
webpki = { version = "0.21", optional = true }
[dev-dependencies]
bytes = "0.5.3"
actix-testing = { version="1.0.0" }
actix-testing = "1.0.0"

View File

@@ -6,7 +6,7 @@ use std::net::SocketAddr;
use either::Either;
/// Connect request
pub trait Address: Unpin {
pub trait Address: Unpin + 'static {
/// Host name of the request
fn host(&self) -> &str;
@@ -43,7 +43,7 @@ pub struct Connect<T> {
}
impl<T: Address> Connect<T> {
/// Create `Connect` instance by spliting the string by ':' and convert the second part to u16
/// Create `Connect` instance by splitting the string by ':' and convert the second part to u16
pub fn new(req: T) -> Connect<T> {
let (_, port) = parse(req.host());
Connect {
@@ -53,7 +53,8 @@ impl<T: Address> Connect<T> {
}
}
/// Create new `Connect` instance from host and address. Connector skips name resolution stage for such connect messages.
/// Create new `Connect` instance from host and address. Connector skips name resolution stage
/// for such connect messages.
pub fn with(req: T, addr: SocketAddr) -> Connect<T> {
Connect {
req,
@@ -102,7 +103,7 @@ impl<T: Address> Connect<T> {
self.req.port().unwrap_or(self.port)
}
/// Preresolved addresses of the request.
/// Pre-resolved addresses of the request.
pub fn addrs(&self) -> ConnectAddrsIter<'_> {
let inner = match self.addr {
None => Either::Left(None),
@@ -113,7 +114,7 @@ impl<T: Address> Connect<T> {
ConnectAddrsIter { inner }
}
/// Takes preresolved addresses of the request.
/// Takes pre-resolved addresses of the request.
pub fn take_addrs(&mut self) -> ConnectTakeAddrsIter {
let inner = match self.addr.take() {
None => Either::Left(None),

View File

@@ -8,12 +8,12 @@ use std::task::{Context, Poll};
use actix_rt::net::TcpStream;
use actix_service::{Service, ServiceFactory};
use futures::future::{err, ok, BoxFuture, Either, FutureExt, Ready};
use futures_util::future::{err, ok, BoxFuture, Either, FutureExt, Ready};
use super::connect::{Address, Connect, Connection};
use super::error::ConnectError;
/// Tcp connector service factory
/// TCP connector service factory
#[derive(Debug)]
pub struct TcpConnectorFactory<T>(PhantomData<T>);
@@ -22,7 +22,7 @@ impl<T> TcpConnectorFactory<T> {
TcpConnectorFactory(PhantomData)
}
/// Create tcp connector service
/// Create TCP connector service
pub fn service(&self) -> TcpConnector<T> {
TcpConnector(PhantomData)
}
@@ -54,7 +54,7 @@ impl<T: Address> ServiceFactory for TcpConnectorFactory<T> {
}
}
/// Tcp connector service
/// TCP connector service
#[derive(Default, Debug)]
pub struct TcpConnector<T>(PhantomData<T>);
@@ -74,6 +74,7 @@ impl<T: Address> Service for TcpConnector<T> {
type Request = Connect<T>;
type Response = Connection<T, TcpStream>;
type Error = ConnectError;
#[allow(clippy::type_complexity)]
type Future = Either<TcpConnectorResponse<T>, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@@ -88,13 +89,13 @@ impl<T: Address> Service for TcpConnector<T> {
Either::Left(TcpConnectorResponse::new(req, port, addr))
} else {
error!("TCP connector: got unresolved address");
Either::Right(err(ConnectError::Unresolverd))
Either::Right(err(ConnectError::Unresolved))
}
}
}
#[doc(hidden)]
/// Tcp stream connector response future
/// TCP stream connector response future
pub struct TcpConnectorResponse<T> {
req: Option<T>,
port: u16,

View File

@@ -18,9 +18,9 @@ pub enum ConnectError {
/// Unresolved host name
#[display(fmt = "Connector received `Connect` method with unresolved host")]
Unresolverd,
Unresolved,
/// Connection io error
/// Connection IO error
#[display(fmt = "{}", _0)]
Io(io::Error),
}

View File

@@ -1,11 +1,11 @@
//! Actix connect - tcp connector service
//! TCP connector service for Actix ecosystem.
//!
//! ## Package feature
//!
//! * `openssl` - enables ssl support via `openssl` crate
//! * `rustls` - enables ssl support via `rustls` crate
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)]
#![deny(rust_2018_idioms)]
#![recursion_limit = "128"]
#[macro_use]
@@ -25,7 +25,7 @@ use actix_rt::{net::TcpStream, Arbiter};
use actix_service::{pipeline, pipeline_factory, Service, ServiceFactory};
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
use trust_dns_resolver::system_conf::read_system_conf;
use trust_dns_resolver::AsyncResolver;
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
pub mod resolver {
pub use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
@@ -39,17 +39,18 @@ pub use self::error::ConnectError;
pub use self::resolve::{Resolver, ResolverFactory};
pub use self::service::{ConnectService, ConnectServiceFactory, TcpConnectService};
pub fn start_resolver(cfg: ResolverConfig, opts: ResolverOpts) -> AsyncResolver {
let (resolver, bg) = AsyncResolver::new(cfg, opts);
actix_rt::spawn(bg);
resolver
pub async fn start_resolver(
cfg: ResolverConfig,
opts: ResolverOpts,
) -> Result<AsyncResolver, ConnectError> {
Ok(AsyncResolver::tokio(cfg, opts).await?)
}
struct DefaultResolver(AsyncResolver);
pub(crate) fn get_default_resolver() -> AsyncResolver {
pub(crate) async fn get_default_resolver() -> Result<AsyncResolver, ConnectError> {
if Arbiter::contains_item::<DefaultResolver>() {
Arbiter::get_item(|item: &DefaultResolver| item.0.clone())
Ok(Arbiter::get_item(|item: &DefaultResolver| item.0.clone()))
} else {
let (cfg, opts) = match read_system_conf() {
Ok((cfg, opts)) => (cfg, opts),
@@ -59,28 +60,27 @@ pub(crate) fn get_default_resolver() -> AsyncResolver {
}
};
let (resolver, bg) = AsyncResolver::new(cfg, opts);
actix_rt::spawn(bg);
let resolver = AsyncResolver::tokio(cfg, opts).await?;
Arbiter::set_item(DefaultResolver(resolver.clone()));
resolver
Ok(resolver)
}
}
pub fn start_default_resolver() -> AsyncResolver {
get_default_resolver()
pub async fn start_default_resolver() -> Result<AsyncResolver, ConnectError> {
get_default_resolver().await
}
/// Create tcp connector service
pub fn new_connector<T: Address>(
/// Create TCP connector service.
pub fn new_connector<T: Address + 'static>(
resolver: AsyncResolver,
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError>
+ Clone {
pipeline(Resolver::new(resolver)).and_then(TcpConnector::new())
}
/// Create tcp connector service
pub fn new_connector_factory<T: Address>(
/// Create TCP connector service factory.
pub fn new_connector_factory<T: Address + 'static>(
resolver: AsyncResolver,
) -> impl ServiceFactory<
Config = (),
@@ -92,15 +92,15 @@ pub fn new_connector_factory<T: Address>(
pipeline_factory(ResolverFactory::new(resolver)).and_then(TcpConnectorFactory::new())
}
/// Create connector service with default parameters
pub fn default_connector<T: Address>(
/// Create connector service with default parameters.
pub fn default_connector<T: Address + 'static>(
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError>
+ Clone {
pipeline(Resolver::default()).and_then(TcpConnector::new())
}
/// Create connector service factory with default parameters
pub fn default_connector_factory<T: Address>() -> impl ServiceFactory<
/// Create connector service factory with default parameters.
pub fn default_connector_factory<T: Address + 'static>() -> impl ServiceFactory<
Config = (),
Request = Connect<T>,
Response = Connection<T, TcpStream>,

View File

@@ -5,9 +5,9 @@ use std::pin::Pin;
use std::task::{Context, Poll};
use actix_service::{Service, ServiceFactory};
use futures::future::{ok, Either, Ready};
use trust_dns_resolver::lookup_ip::LookupIpFuture;
use trust_dns_resolver::{AsyncResolver, Background};
use futures_util::future::{ok, Either, Ready};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use trust_dns_resolver::{error::ResolveError, lookup_ip::LookupIp};
use crate::connect::{Address, Connect};
use crate::error::ConnectError;
@@ -106,7 +106,11 @@ impl<T: Address> Service for Resolver<T> {
type Request = Connect<T>;
type Response = Connect<T>;
type Error = ConnectError;
type Future = Either<ResolverFuture<T>, Ready<Result<Connect<T>, Self::Error>>>;
#[allow(clippy::type_complexity)]
type Future = Either<
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>,
Ready<Result<Connect<T>, Self::Error>>,
>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
@@ -119,32 +123,48 @@ impl<T: Address> Service for Resolver<T> {
req.addr = Some(either::Either::Left(SocketAddr::new(ip, req.port())));
Either::Right(ok(req))
} else {
trace!("DNS resolver: resolving host {:?}", req.host());
if self.resolver.is_none() {
self.resolver = Some(get_default_resolver());
}
Either::Left(ResolverFuture::new(req, self.resolver.as_ref().unwrap()))
let resolver = self.resolver.as_ref().map(AsyncResolver::clone);
Either::Left(Box::pin(async move {
trace!("DNS resolver: resolving host {:?}", req.host());
let resolver = if let Some(resolver) = resolver {
resolver
} else {
get_default_resolver()
.await
.expect("Failed to get default resolver")
};
ResolverFuture::new(req, &resolver).await
}))
}
}
}
type LookupIpFuture = Pin<Box<dyn Future<Output = Result<LookupIp, ResolveError>>>>;
#[doc(hidden)]
/// Resolver future
pub struct ResolverFuture<T: Address> {
req: Option<Connect<T>>,
lookup: Background<LookupIpFuture>,
lookup: LookupIpFuture,
}
impl<T: Address> ResolverFuture<T> {
pub fn new(req: Connect<T>, resolver: &AsyncResolver) -> Self {
let lookup = if let Some(host) = req.host().splitn(2, ':').next() {
resolver.lookup_ip(host)
let host = if let Some(host) = req.host().splitn(2, ':').next() {
host
} else {
resolver.lookup_ip(req.host())
req.host()
};
// Clone data to be moved to the lookup future
let host_clone = host.to_owned();
let resolver_clone = resolver.clone();
ResolverFuture {
lookup,
lookup: Box::pin(async move {
let resolver = resolver_clone;
resolver.lookup_ip(host_clone).await
}),
req: Some(req),
}
}

View File

@@ -5,8 +5,8 @@ use std::task::{Context, Poll};
use actix_rt::net::TcpStream;
use actix_service::{Service, ServiceFactory};
use either::Either;
use futures::future::{ok, Ready};
use trust_dns_resolver::AsyncResolver;
use futures_util::future::{ok, Ready};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use crate::connect::{Address, Connect, Connection};
use crate::connector::{TcpConnector, TcpConnectorFactory};
@@ -114,6 +114,7 @@ enum ConnectState<T: Address> {
}
impl<T: Address> ConnectState<T> {
#[allow(clippy::type_complexity)]
fn poll(
&mut self,
cx: &mut Context<'_>,

View File

@@ -10,14 +10,14 @@ pub use tokio_openssl::{HandshakeError, SslStream};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream;
use actix_service::{Service, ServiceFactory};
use futures::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready};
use trust_dns_resolver::AsyncResolver;
use futures_util::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use crate::{
Address, Connect, ConnectError, ConnectService, ConnectServiceFactory, Connection,
};
/// Openssl connector factory
/// OpenSSL connector factory
pub struct OpensslConnector<T, U> {
connector: SslConnector,
_t: PhantomData<(T, U)>,
@@ -97,6 +97,7 @@ where
type Request = Connection<T, U>;
type Response = Connection<T, SslStream<U>>;
type Error = io::Error;
#[allow(clippy::type_complexity)]
type Future = Either<ConnectAsyncExt<T, U>, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@@ -164,7 +165,7 @@ impl<T> OpensslConnectServiceFactory<T> {
}
}
/// Construct new connect service with custom dns resolver
/// Construct new connect service with custom DNS resolver
pub fn with_resolver(connector: SslConnector, resolver: AsyncResolver) -> Self {
OpensslConnectServiceFactory {
tcp: ConnectServiceFactory::with_resolver(resolver),
@@ -172,7 +173,7 @@ impl<T> OpensslConnectServiceFactory<T> {
}
}
/// Construct openssl connect service
/// Construct OpenSSL connect service
pub fn service(&self) -> OpensslConnectService<T> {
OpensslConnectService {
tcp: self.tcp.service(),
@@ -243,7 +244,7 @@ impl<T: Address> Future for OpensslConnectServiceResponse<T> {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(ref mut fut) = self.fut1 {
match futures::ready!(Pin::new(fut).poll(cx)) {
match futures_util::ready!(Pin::new(fut).poll(cx)) {
Ok(res) => {
let _ = self.fut1.take();
self.fut2 = Some(self.openssl.call(res));
@@ -253,7 +254,7 @@ impl<T: Address> Future for OpensslConnectServiceResponse<T> {
}
if let Some(ref mut fut) = self.fut2 {
match futures::ready!(Pin::new(fut).poll(cx)) {
match futures_util::ready!(Pin::new(fut).poll(cx)) {
Ok(connect) => Poll::Ready(Ok(connect.into_parts().0)),
Err(e) => Poll::Ready(Err(ConnectError::Io(io::Error::new(
io::ErrorKind::Other,

View File

@@ -10,7 +10,7 @@ pub use tokio_rustls::{client::TlsStream, rustls::ClientConfig};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::{Service, ServiceFactory};
use futures::future::{ok, Ready};
use futures_util::future::{ok, Ready};
use tokio_rustls::{Connect, TlsConnector};
use webpki::DNSNameRef;
@@ -38,7 +38,7 @@ where
{
pub fn service(connector: Arc<ClientConfig>) -> RustlsConnectorService<T, U> {
RustlsConnectorService {
connector: connector,
connector,
_t: PhantomData,
}
}
@@ -126,7 +126,7 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
Poll::Ready(
futures::ready!(Pin::new(&mut this.fut).poll(cx)).map(|stream| {
futures_util::ready!(Pin::new(&mut this.fut).poll(cx)).map(|stream| {
let s = this.stream.take().unwrap();
trace!("SSL Handshake success: {:?}", s.host());
s.replace(stream).1

View File

@@ -5,7 +5,7 @@ use actix_rt::net::TcpStream;
use actix_service::{fn_service, Service, ServiceFactory};
use actix_testing::TestServer;
use bytes::Bytes;
use futures::SinkExt;
use futures_util::sink::SinkExt;
use actix_connect::resolver::{ResolverConfig, ResolverOpts};
use actix_connect::Connect;
@@ -14,12 +14,10 @@ use actix_connect::Connect;
#[actix_rt::test]
async fn test_string() {
let srv = TestServer::with(|| {
fn_service(|io: TcpStream| {
async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});
@@ -33,12 +31,10 @@ async fn test_string() {
#[actix_rt::test]
async fn test_rustls_string() {
let srv = TestServer::with(|| {
fn_service(|io: TcpStream| {
async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});
@@ -51,16 +47,14 @@ async fn test_rustls_string() {
#[actix_rt::test]
async fn test_static_str() {
let srv = TestServer::with(|| {
fn_service(|io: TcpStream| {
async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});
let resolver = actix_connect::start_default_resolver();
let resolver = actix_connect::start_default_resolver().await.unwrap();
let mut conn = actix_connect::new_connector(resolver.clone());
let con = conn.call(Connect::with("10", srv.addr())).await.unwrap();
@@ -75,17 +69,17 @@ async fn test_static_str() {
#[actix_rt::test]
async fn test_new_service() {
let srv = TestServer::with(|| {
fn_service(|io: TcpStream| {
async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});
let resolver =
actix_connect::start_resolver(ResolverConfig::default(), ResolverOpts::default());
actix_connect::start_resolver(ResolverConfig::default(), ResolverOpts::default())
.await
.unwrap();
let factory = actix_connect::new_connector_factory(resolver);
@@ -94,18 +88,16 @@ async fn test_new_service() {
assert_eq!(con.peer_addr().unwrap(), srv.addr());
}
#[cfg(feature = "openssl")]
#[cfg(all(feature = "openssl", feature = "uri"))]
#[actix_rt::test]
async fn test_uri() {
async fn test_openssl_uri() {
use std::convert::TryFrom;
let srv = TestServer::with(|| {
fn_service(|io: TcpStream| {
async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});
@@ -115,18 +107,16 @@ async fn test_uri() {
assert_eq!(con.peer_addr().unwrap(), srv.addr());
}
#[cfg(feature = "rustls")]
#[cfg(all(feature = "rustls", feature = "uri"))]
#[actix_rt::test]
async fn test_rustls_uri() {
use std::convert::TryFrom;
let srv = TestServer::with(|| {
fn_service(|io: TcpStream| {
async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});

View File

@@ -1,33 +0,0 @@
# Changes
## [0.5.0] - 2019-12-29
* Simplify state management
* Allow to set custom output stream
* Removed disconnect callback
## [0.4.1] - 2019-12-11
* Disconnect callback accepts owned state
## [0.4.0] - 2019-12-11
* Remove `E` param
## [0.3.0-alpha.3] - 2019-12-07
* Migrate to tokio 0.2
## [0.3.0-alpha.2] - 2019-12-02
* Migrate to `std::future`
## [0.1.1] - 2019-10-14
* Re-register task on every dispatcher poll.
## [0.1.0] - 2019-09-25
* Initial release

View File

@@ -1,31 +0,0 @@
[package]
name = "actix-ioframe"
version = "0.5.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix framed service"
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-ioframe/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
edition = "2018"
[lib]
name = "actix_ioframe"
path = "src/lib.rs"
[dependencies]
actix-service = "1.0.1"
actix-codec = "0.2.0"
actix-utils = "1.0.4"
actix-rt = "1.0.0"
bytes = "0.5.3"
either = "1.5.3"
futures = "0.3.1"
pin-project = "0.4.6"
log = "0.4"
[dev-dependencies]
actix-connect = "1.0.0"
actix-testing = "1.0.0"

3
actix-ioframe/README.md Normal file
View File

@@ -0,0 +1,3 @@
# actix-ioframe
**This crate has been deprecated and removed.**

View File

@@ -1,122 +0,0 @@
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
use actix_utils::mpsc::Receiver;
use futures::Stream;
pub struct Connect<Io, Codec>
where
Codec: Encoder + Decoder,
{
io: Io,
_t: PhantomData<Codec>,
}
impl<Io, Codec> Connect<Io, Codec>
where
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
{
pub(crate) fn new(io: Io) -> Self {
Self {
io,
_t: PhantomData,
}
}
pub fn codec(
self,
codec: Codec,
) -> ConnectResult<Io, (), Codec, Receiver<<Codec as Encoder>::Item>> {
ConnectResult {
state: (),
out: None,
framed: Framed::new(self.io, codec),
}
}
}
#[pin_project::pin_project]
pub struct ConnectResult<Io, St, Codec: Encoder + Decoder, Out> {
pub(crate) state: St,
pub(crate) out: Option<Out>,
pub(crate) framed: Framed<Io, Codec>,
}
impl<Io, St, Codec: Encoder + Decoder, Out: Unpin> ConnectResult<Io, St, Codec, Out> {
#[inline]
pub fn get_ref(&self) -> &Io {
self.framed.get_ref()
}
#[inline]
pub fn get_mut(&mut self) -> &mut Io {
self.framed.get_mut()
}
pub fn out<U>(self, out: U) -> ConnectResult<Io, St, Codec, U>
where
U: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
ConnectResult {
state: self.state,
framed: self.framed,
out: Some(out),
}
}
#[inline]
pub fn state<S>(self, state: S) -> ConnectResult<Io, S, Codec, Out> {
ConnectResult {
state,
framed: self.framed,
out: self.out,
}
}
}
impl<Io, St, Codec, Out> Stream for ConnectResult<Io, St, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
{
type Item = Result<<Codec as Decoder>::Item, <Codec as Decoder>::Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.project().framed.next_item(cx)
}
}
impl<Io, St, Codec, Out> futures::Sink<<Codec as Encoder>::Item>
for ConnectResult<Io, St, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
{
type Error = <Codec as Encoder>::Error;
fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.framed.is_write_ready() {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
fn start_send(
self: Pin<&mut Self>,
item: <Codec as Encoder>::Item,
) -> Result<(), Self::Error> {
self.project().framed.write(item)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.get_mut().framed.flush(cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.get_mut().framed.close(cx)
}
}

View File

@@ -1,242 +0,0 @@
//! Framed dispatcher service and related utilities
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
use actix_service::Service;
use actix_utils::mpsc;
use futures::Stream;
use log::debug;
use crate::error::ServiceError;
type Request<U> = <U as Decoder>::Item;
type Response<U> = <U as Encoder>::Item;
/// FramedTransport - is a future that reads frames from Framed object
/// and pass then to the service.
pub(crate) struct Dispatcher<S, T, U, Out>
where
S: Service<Request = Request<U>, Response = Option<Response<U>>>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Encoder + Decoder,
<U as Encoder>::Item: 'static,
<U as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <U as Encoder>::Item> + Unpin,
{
service: S,
sink: Option<Out>,
state: FramedState<S, U>,
framed: Framed<T, U>,
rx: mpsc::Receiver<Result<<U as Encoder>::Item, S::Error>>,
}
impl<S, T, U, Out> Dispatcher<S, T, U, Out>
where
S: Service<Request = Request<U>, Response = Option<Response<U>>>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder,
<U as Encoder>::Item: 'static,
<U as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <U as Encoder>::Item> + Unpin,
{
pub(crate) fn new(framed: Framed<T, U>, service: S, sink: Option<Out>) -> Self {
Dispatcher {
sink,
service,
framed,
rx: mpsc::channel().1,
state: FramedState::Processing,
}
}
}
enum FramedState<S: Service, U: Encoder + Decoder> {
Processing,
Error(ServiceError<S::Error, U>),
FramedError(ServiceError<S::Error, U>),
FlushAndStop,
Stopping,
}
impl<S: Service, U: Encoder + Decoder> FramedState<S, U> {
fn take_error(&mut self) -> ServiceError<S::Error, U> {
match std::mem::replace(self, FramedState::Processing) {
FramedState::Error(err) => err,
_ => panic!(),
}
}
fn take_framed_error(&mut self) -> ServiceError<S::Error, U> {
match std::mem::replace(self, FramedState::Processing) {
FramedState::FramedError(err) => err,
_ => panic!(),
}
}
}
impl<S, T, U, Out> Dispatcher<S, T, U, Out>
where
S: Service<Request = Request<U>, Response = Option<Response<U>>>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder,
<U as Encoder>::Item: 'static,
<U as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <U as Encoder>::Item> + Unpin,
{
fn poll_read(&mut self, cx: &mut Context<'_>) -> bool {
loop {
match self.service.poll_ready(cx) {
Poll::Ready(Ok(_)) => {
let item = match self.framed.next_item(cx) {
Poll::Ready(Some(Ok(el))) => el,
Poll::Ready(Some(Err(err))) => {
self.state = FramedState::FramedError(ServiceError::Decoder(err));
return true;
}
Poll::Pending => return false,
Poll::Ready(None) => {
log::trace!("Client disconnected");
self.state = FramedState::Stopping;
return true;
}
};
let tx = self.rx.sender();
let fut = self.service.call(item);
actix_rt::spawn(async move {
let item = fut.await;
let item = match item {
Ok(Some(item)) => Ok(item),
Ok(None) => return,
Err(err) => Err(err),
};
let _ = tx.send(item);
});
}
Poll::Pending => return false,
Poll::Ready(Err(err)) => {
self.state = FramedState::Error(ServiceError::Service(err));
return true;
}
}
}
}
/// write to framed object
fn poll_write(&mut self, cx: &mut Context<'_>) -> bool {
loop {
while !self.framed.is_write_buf_full() {
match Pin::new(&mut self.rx).poll_next(cx) {
Poll::Ready(Some(Ok(msg))) => {
if let Err(err) = self.framed.write(msg) {
self.state = FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
continue;
}
Poll::Ready(Some(Err(err))) => {
self.state = FramedState::Error(ServiceError::Service(err));
return true;
}
Poll::Ready(None) | Poll::Pending => (),
}
if self.sink.is_some() {
match Pin::new(self.sink.as_mut().unwrap()).poll_next(cx) {
Poll::Ready(Some(msg)) => {
if let Err(err) = self.framed.write(msg) {
self.state =
FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
continue;
}
Poll::Ready(None) => {
let _ = self.sink.take();
self.state = FramedState::FlushAndStop;
return true;
}
Poll::Pending => (),
}
}
break;
}
if !self.framed.is_write_buf_empty() {
match self.framed.flush(cx) {
Poll::Pending => break,
Poll::Ready(Ok(_)) => (),
Poll::Ready(Err(err)) => {
debug!("Error sending data: {:?}", err);
self.state = FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
}
} else {
break;
}
}
false
}
pub(crate) fn poll(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Result<(), ServiceError<S::Error, U>>> {
match self.state {
FramedState::Processing => loop {
let read = self.poll_read(cx);
let write = self.poll_write(cx);
if read || write {
continue;
} else {
return Poll::Pending;
}
},
FramedState::Error(_) => {
// flush write buffer
if !self.framed.is_write_buf_empty() {
if let Poll::Pending = self.framed.flush(cx) {
return Poll::Pending;
}
}
Poll::Ready(Err(self.state.take_error()))
}
FramedState::FlushAndStop => {
// drain service responses
match Pin::new(&mut self.rx).poll_next(cx) {
Poll::Ready(Some(Ok(msg))) => {
if let Err(_) = self.framed.write(msg) {
return Poll::Ready(Ok(()));
}
}
Poll::Ready(Some(Err(_))) => return Poll::Ready(Ok(())),
Poll::Ready(None) | Poll::Pending => (),
}
// flush io
if !self.framed.is_write_buf_empty() {
match self.framed.flush(cx) {
Poll::Ready(Err(err)) => {
debug!("Error sending data: {:?}", err);
}
Poll::Pending => {
return Poll::Pending;
}
Poll::Ready(_) => (),
}
};
Poll::Ready(Ok(()))
}
FramedState::FramedError(_) => Poll::Ready(Err(self.state.take_framed_error())),
FramedState::Stopping => Poll::Ready(Ok(())),
}
}
}

View File

@@ -1,49 +0,0 @@
use std::fmt;
use actix_codec::{Decoder, Encoder};
/// Framed service errors
pub enum ServiceError<E, U: Encoder + Decoder> {
/// Inner service error
Service(E),
/// Encoder parse error
Encoder(<U as Encoder>::Error),
/// Decoder parse error
Decoder(<U as Decoder>::Error),
}
impl<E, U: Encoder + Decoder> From<E> for ServiceError<E, U> {
fn from(err: E) -> Self {
ServiceError::Service(err)
}
}
impl<E, U: Encoder + Decoder> fmt::Debug for ServiceError<E, U>
where
E: fmt::Debug,
<U as Encoder>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ServiceError::Service(ref e) => write!(fmt, "ServiceError::Service({:?})", e),
ServiceError::Encoder(ref e) => write!(fmt, "ServiceError::Encoder({:?})", e),
ServiceError::Decoder(ref e) => write!(fmt, "ServiceError::Encoder({:?})", e),
}
}
}
impl<E, U: Encoder + Decoder> fmt::Display for ServiceError<E, U>
where
E: fmt::Display,
<U as Encoder>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ServiceError::Service(ref e) => write!(fmt, "{}", e),
ServiceError::Encoder(ref e) => write!(fmt, "{:?}", e),
ServiceError::Decoder(ref e) => write!(fmt, "{:?}", e),
}
}
}

View File

@@ -1,11 +0,0 @@
// #![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity, clippy::too_many_arguments)]
mod connect;
mod dispatcher;
mod error;
mod service;
pub use self::connect::{Connect, ConnectResult};
pub use self::error::ServiceError;
pub use self::service::{Builder, FactoryBuilder};

View File

@@ -1,416 +0,0 @@
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory};
use either::Either;
use futures::{ready, Stream};
use pin_project::project;
use crate::connect::{Connect, ConnectResult};
use crate::dispatcher::Dispatcher;
use crate::error::ServiceError;
type RequestItem<U> = <U as Decoder>::Item;
type ResponseItem<U> = Option<<U as Encoder>::Item>;
/// Service builder - structure that follows the builder pattern
/// for building instances for framed services.
pub struct Builder<St, C, Io, Codec, Out> {
connect: C,
_t: PhantomData<(St, Io, Codec, Out)>,
}
impl<St, C, Io, Codec, Out> Builder<St, C, Io, Codec, Out>
where
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
Io: AsyncRead + AsyncWrite,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
/// Construct framed handler service with specified connect service
pub fn new<F>(connect: F) -> Builder<St, C, Io, Codec, Out>
where
F: IntoService<C>,
Io: AsyncRead + AsyncWrite,
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
Codec: Decoder + Encoder,
Out: Stream<Item = <Codec as Encoder>::Item>,
{
Builder {
connect: connect.into_service(),
_t: PhantomData,
}
}
/// Provide stream items handler service and construct service factory.
pub fn build<F, T>(self, service: F) -> FramedServiceImpl<St, C, T, Io, Codec, Out>
where
F: IntoServiceFactory<T>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
{
FramedServiceImpl {
connect: self.connect,
handler: Rc::new(service.into_factory()),
_t: PhantomData,
}
}
}
/// Service builder - structure that follows the builder pattern
/// for building instances for framed services.
pub struct FactoryBuilder<St, C, Io, Codec, Out> {
connect: C,
_t: PhantomData<(St, Io, Codec, Out)>,
}
impl<St, C, Io, Codec, Out> FactoryBuilder<St, C, Io, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io, Codec>,
Response = ConnectResult<Io, St, Codec, Out>,
>,
Codec: Decoder + Encoder,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
/// Construct framed handler new service with specified connect service
pub fn new<F>(connect: F) -> FactoryBuilder<St, C, Io, Codec, Out>
where
F: IntoServiceFactory<C>,
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io, Codec>,
Response = ConnectResult<Io, St, Codec, Out>,
>,
Codec: Decoder + Encoder,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
FactoryBuilder {
connect: connect.into_factory(),
_t: PhantomData,
}
}
pub fn build<F, T, Cfg>(self, service: F) -> FramedService<St, C, T, Io, Codec, Out, Cfg>
where
F: IntoServiceFactory<T>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
{
FramedService {
connect: self.connect,
handler: Rc::new(service.into_factory()),
_t: PhantomData,
}
}
}
pub struct FramedService<St, C, T, Io, Codec, Out, Cfg> {
connect: C,
handler: Rc<T>,
_t: PhantomData<(St, Io, Codec, Out, Cfg)>,
}
impl<St, C, T, Io, Codec, Out, Cfg> ServiceFactory
for FramedService<St, C, T, Io, Codec, Out, Cfg>
where
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io, Codec>,
Response = ConnectResult<Io, St, Codec, Out>,
>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
type Config = Cfg;
type Request = Io;
type Response = ();
type Error = ServiceError<C::Error, Codec>;
type InitError = C::InitError;
type Service = FramedServiceImpl<St, C::Service, T, Io, Codec, Out>;
type Future = FramedServiceResponse<St, C, T, Io, Codec, Out>;
fn new_service(&self, _: Cfg) -> Self::Future {
// create connect service and then create service impl
FramedServiceResponse {
fut: self.connect.new_service(()),
handler: self.handler.clone(),
}
}
}
#[pin_project::pin_project]
pub struct FramedServiceResponse<St, C, T, Io, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io, Codec>,
Response = ConnectResult<Io, St, Codec, Out>,
>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
#[pin]
fut: C::Future,
handler: Rc<T>,
}
impl<St, C, T, Io, Codec, Out> Future for FramedServiceResponse<St, C, T, Io, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io, Codec>,
Response = ConnectResult<Io, St, Codec, Out>,
>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
type Output = Result<FramedServiceImpl<St, C::Service, T, Io, Codec, Out>, C::InitError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let connect = ready!(this.fut.poll(cx))?;
Poll::Ready(Ok(FramedServiceImpl {
connect,
handler: this.handler.clone(),
_t: PhantomData,
}))
}
}
pub struct FramedServiceImpl<St, C, T, Io, Codec, Out> {
connect: C,
handler: Rc<T>,
_t: PhantomData<(St, Io, Codec, Out)>,
}
impl<St, C, T, Io, Codec, Out> Service for FramedServiceImpl<St, C, T, Io, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
type Request = Io;
type Response = ();
type Error = ServiceError<C::Error, Codec>;
type Future = FramedServiceImplResponse<St, Io, Codec, Out, C, T>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.connect.poll_ready(cx).map_err(|e| e.into())
}
fn call(&mut self, req: Io) -> Self::Future {
FramedServiceImplResponse {
inner: FramedServiceImplResponseInner::Connect(
self.connect.call(Connect::new(req)),
self.handler.clone(),
),
}
}
}
#[pin_project::pin_project]
pub struct FramedServiceImplResponse<St, Io, Codec, Out, C, T>
where
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
#[pin]
inner: FramedServiceImplResponseInner<St, Io, Codec, Out, C, T>,
}
impl<St, Io, Codec, Out, C, T> Future for FramedServiceImplResponse<St, Io, Codec, Out, C, T>
where
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
type Output = Result<(), ServiceError<C::Error, Codec>>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
loop {
match this.inner.poll(cx) {
Either::Left(new) => {
this = self.as_mut().project();
this.inner.set(new)
}
Either::Right(poll) => return poll,
};
}
}
}
#[pin_project::pin_project]
enum FramedServiceImplResponseInner<St, Io, Codec, Out, C, T>
where
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
Connect(#[pin] C::Future, Rc<T>),
Handler(#[pin] T::Future, Option<Framed<Io, Codec>>, Option<Out>),
Dispatcher(Dispatcher<T::Service, Io, Codec, Out>),
}
impl<St, Io, Codec, Out, C, T> FramedServiceImplResponseInner<St, Io, Codec, Out, C, T>
where
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
#[project]
fn poll(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Either<
FramedServiceImplResponseInner<St, Io, Codec, Out, C, T>,
Poll<Result<(), ServiceError<C::Error, Codec>>>,
> {
#[project]
match self.project() {
FramedServiceImplResponseInner::Connect(fut, handler) => match fut.poll(cx) {
Poll::Ready(Ok(res)) => Either::Left(FramedServiceImplResponseInner::Handler(
handler.new_service(res.state),
Some(res.framed),
res.out,
)),
Poll::Pending => Either::Right(Poll::Pending),
Poll::Ready(Err(e)) => Either::Right(Poll::Ready(Err(e.into()))),
},
FramedServiceImplResponseInner::Handler(fut, framed, out) => {
match fut.poll(cx) {
Poll::Ready(Ok(handler)) => {
Either::Left(FramedServiceImplResponseInner::Dispatcher(
Dispatcher::new(framed.take().unwrap(), handler, out.take()),
))
}
Poll::Pending => Either::Right(Poll::Pending),
Poll::Ready(Err(e)) => Either::Right(Poll::Ready(Err(e.into()))),
}
}
FramedServiceImplResponseInner::Dispatcher(ref mut fut) => {
Either::Right(fut.poll(cx))
}
}
}
}

View File

@@ -1,55 +0,0 @@
use std::cell::Cell;
use std::rc::Rc;
use actix_codec::BytesCodec;
use actix_service::{fn_factory_with_config, fn_service, IntoService, Service};
use actix_testing::TestServer;
use actix_utils::mpsc;
use bytes::{Bytes, BytesMut};
use futures::future::ok;
use actix_ioframe::{Builder, Connect, FactoryBuilder};
#[derive(Clone)]
struct State(Option<mpsc::Sender<Bytes>>);
#[actix_rt::test]
async fn test_basic() {
let client_item = Rc::new(Cell::new(false));
let srv = TestServer::with(move || {
FactoryBuilder::new(fn_service(|conn: Connect<_, _>| {
ok(conn.codec(BytesCodec).state(State(None)))
}))
// echo
.build(fn_service(|t: BytesMut| ok(Some(t.freeze()))))
});
let item = client_item.clone();
let mut client = Builder::new(fn_service(move |conn: Connect<_, _>| {
async move {
let (tx, rx) = mpsc::channel();
let _ = tx.send(Bytes::from_static(b"Hello"));
Ok(conn.codec(BytesCodec).out(rx).state(State(Some(tx))))
}
}))
.build(fn_factory_with_config(move |mut cfg: State| {
let item = item.clone();
ok((move |t: BytesMut| {
assert_eq!(t.freeze(), Bytes::from_static(b"Hello"));
item.set(true);
// drop Sender, which will close connection
cfg.0.take();
ok::<_, ()>(None)
})
.into_service())
}));
let conn = actix_connect::default_connector()
.call(actix_connect::Connect::with(String::new(), srv.addr()))
.await
.unwrap();
client.call(conn.into_parts().0).await.unwrap();
assert!(client_item.get());
}

1
actix-macros/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/wip

9
actix-macros/CHANGES.md Normal file
View File

@@ -0,0 +1,9 @@
# CHANGES
## 0.1.2 - 2020-05-18
### Changed
* Forward actix_rt::test arguments to test function [#127]
[#127]: https://github.com/actix/actix-net/pull/127

View File

@@ -1,21 +1,23 @@
[package]
name = "actix-macros"
version = "0.1.1"
version = "0.1.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix runtime macros"
repository = "https://github.com/actix/actix-net"
documentation = "https://docs.rs/actix-macros/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
edition = "2018"
workspace = ".."
[lib]
proc-macro = true
[dependencies]
quote = "^1"
quote = "1.0.3"
syn = { version = "^1", features = ["full"] }
[dev-dependencies]
actix-rt = { version = "1.0.0" }
actix-rt = "1.0"
futures-util = { version = "0.3", default-features = false }
trybuild = "1"

View File

@@ -14,6 +14,7 @@ use quote::quote;
/// println!("Hello world");
/// }
/// ```
#[allow(clippy::needless_doctest_main)]
#[proc_macro_attribute]
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
@@ -54,12 +55,11 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemFn);
let ret = &input.sig.output;
let name = &input.sig.ident;
let body = &input.block;
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
let attrs = &input.attrs;
let vis = &input.vis;
let sig = &mut input.sig;
let body = &input.block;
let mut has_test_attr = false;
for attr in attrs {
@@ -68,7 +68,7 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
}
}
if input.sig.asyncness.is_none() {
if sig.asyncness.is_none() {
return syn::Error::new_spanned(
input.sig.fn_token,
format!("only async fn is supported, {}", input.sig.ident),
@@ -77,10 +77,12 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
.into();
}
sig.asyncness = None;
let result = if has_test_attr {
quote! {
#(#attrs)*
fn #name() #ret {
#vis #sig {
actix_rt::System::new("test")
.block_on(async { #body })
}
@@ -89,7 +91,7 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
quote! {
#[test]
#(#attrs)*
fn #name() #ret {
#vis #sig {
actix_rt::System::new("test")
.block_on(async { #body })
}

View File

@@ -0,0 +1,9 @@
#[test]
fn compile_macros() {
let t = trybuild::TestCases::new();
t.pass("tests/trybuild/main-01-basic.rs");
t.compile_fail("tests/trybuild/main-02-only-async.rs");
t.pass("tests/trybuild/test-01-basic.rs");
t.pass("tests/trybuild/test-02-keep-attrs.rs");
}

View File

@@ -0,0 +1,4 @@
#[actix_rt::main]
async fn main() {
println!("Hello world");
}

View File

@@ -0,0 +1,4 @@
#[actix_rt::main]
fn main() {
futures_util::future::ready(()).await
}

View File

@@ -0,0 +1,14 @@
error: only async fn is supported
--> $DIR/main-02-only-async.rs:2:1
|
2 | fn main() {
| ^^
error[E0601]: `main` function not found in crate `$CRATE`
--> $DIR/main-02-only-async.rs:1:1
|
1 | / #[actix_rt::main]
2 | | fn main() {
3 | | futures_util::future::ready(()).await
4 | | }
| |_^ consider adding a `main` function to `$DIR/tests/trybuild/main-02-only-async.rs`

View File

@@ -0,0 +1,6 @@
#[actix_rt::test]
async fn my_test() {
assert!(true);
}
fn main() {}

View File

@@ -0,0 +1,7 @@
#[actix_rt::test]
#[should_panic]
async fn my_test() {
todo!()
}
fn main() {}

View File

@@ -1,5 +1,28 @@
# Changes
## [1.1.1] - 2020-04-30
### Fixed
* Fix memory leak due to [#94] (see [#129] for more detail)
[#129]: https://github.com/actix/actix-net/issues/129
## [1.1.0] - 2020-04-08
**This version has been yanked.**
### Added
* Expose `System::is_set` to check if current system has ben started [#99]
* Add `Arbiter::is_running` to check if event loop is running [#124]
* Add `Arbiter::local_join` associated function
to get be able to `await` for spawned futures [#94]
[#94]: https://github.com/actix/actix-net/pull/94
[#99]: https://github.com/actix/actix-net/pull/99
[#124]: https://github.com/actix/actix-net/pull/124
## [1.0.0] - 2019-12-11
* Update dependencies

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-rt"
version = "1.0.0"
version = "1.1.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix runtime"
keywords = ["network", "framework", "async", "futures"]
@@ -8,7 +8,7 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-rt/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
edition = "2018"
[lib]
@@ -18,6 +18,8 @@ path = "src/lib.rs"
[dependencies]
actix-macros = "0.1.0"
actix-threadpool = "0.3"
futures = "0.3.1"
futures-channel = { version = "0.3.4", default-features = false }
futures-util = { version = "0.3.4", default-features = false, features = ["alloc"] }
copyless = "0.1.4"
tokio = { version = "0.2.6", default-features=false, features = ["rt-core", "rt-util", "io-driver", "tcp", "uds", "udp", "time", "signal", "stream"] }
smallvec = "1"
tokio = { version = "0.2.6", default-features = false, features = ["rt-core", "rt-util", "io-driver", "tcp", "uds", "udp", "time", "signal", "stream"] }

View File

@@ -6,19 +6,26 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::task::{Context, Poll};
use std::{fmt, thread};
use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures::channel::oneshot::{channel, Canceled, Sender};
use futures::{future, Future, FutureExt, Stream};
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_channel::oneshot::{channel, Canceled, Sender};
use futures_util::{
future::{self, Future, FutureExt},
stream::Stream,
};
use crate::runtime::Runtime;
use crate::system::System;
use copyless::BoxHelper;
use smallvec::SmallVec;
pub use tokio::task::JoinHandle;
thread_local!(
static ADDR: RefCell<Option<Arbiter>> = RefCell::new(None);
static RUNNING: Cell<bool> = Cell::new(false);
static Q: RefCell<Vec<Pin<Box<dyn Future<Output = ()>>>>> = RefCell::new(Vec::new());
static PENDING: RefCell<SmallVec<[JoinHandle<()>; 8]>> = RefCell::new(SmallVec::new());
static STORAGE: RefCell<HashMap<TypeId, Box<dyn Any>>> = RefCell::new(HashMap::new());
);
@@ -42,8 +49,8 @@ impl fmt::Debug for ArbiterCommand {
#[derive(Debug)]
/// Arbiters provide an asynchronous execution environment for actors, functions
/// and futures. When an Arbiter is created, they spawn a new OS thread, and
/// host an event loop. Some Arbiter functions execute on the current thread.
/// and futures. When an Arbiter is created, it spawns a new OS thread, and
/// hosts an event loop. Some Arbiter functions execute on the current thread.
pub struct Arbiter {
sender: UnboundedSender<ArbiterCommand>,
thread_handle: Option<thread::JoinHandle<()>>,
@@ -83,6 +90,11 @@ impl Arbiter {
})
}
/// Check if current arbiter is running.
pub fn is_running() -> bool {
RUNNING.with(|cell| cell.get())
}
/// Stop arbiter from continuing it's event loop.
pub fn stop(&self) {
let _ = self.sender.unbounded_send(ArbiterCommand::Stop);
@@ -170,13 +182,20 @@ impl Arbiter {
RUNNING.with(move |cell| {
if cell.get() {
// Spawn the future on running executor
tokio::task::spawn_local(future);
let len = PENDING.with(move |cell| {
let mut p = cell.borrow_mut();
p.push(tokio::task::spawn_local(future));
p.len()
});
if len > 7 {
// Before reaching the inline size
tokio::task::spawn_local(CleanupPending);
}
} else {
// Box the future and push it to the queue, this results in double boxing
// because the executor boxes the future again, but works for now
Q.with(move |cell| {
cell.borrow_mut()
.push(unsafe { Pin::new_unchecked(Box::alloc().init(future)) })
cell.borrow_mut().push(Pin::from(Box::alloc().init(future)))
});
}
});
@@ -294,6 +313,39 @@ impl Arbiter {
Ok(())
}
}
/// Returns a future that will be completed once all currently spawned futures
/// have completed.
pub fn local_join() -> impl Future<Output = ()> {
PENDING.with(move |cell| {
let current = cell.replace(SmallVec::new());
future::join_all(current).map(|_| ())
})
}
}
/// Future used for cleaning-up already finished `JoinHandle`s
/// from the `PENDING` list so the vector doesn't grow indefinitely
struct CleanupPending;
impl Future for CleanupPending {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
PENDING.with(move |cell| {
let mut pending = cell.borrow_mut();
let mut i = 0;
while i != pending.len() {
if let Poll::Ready(_) = Pin::new(&mut pending[i]).poll(cx) {
pending.remove(i);
} else {
i += 1;
}
}
});
Poll::Ready(())
}
}
struct ArbiterController {
@@ -329,7 +381,15 @@ impl Future for ArbiterController {
return Poll::Ready(());
}
ArbiterCommand::Execute(fut) => {
tokio::task::spawn_local(fut);
let len = PENDING.with(move |cell| {
let mut p = cell.borrow_mut();
p.push(tokio::task::spawn_local(fut));
p.len()
});
if len > 7 {
// Before reaching the inline size
tokio::task::spawn_local(CleanupPending);
}
}
ArbiterCommand::ExecuteFn(f) => {
f.call_box();

View File

@@ -1,9 +1,9 @@
use std::borrow::Cow;
use std::io;
use futures::channel::mpsc::unbounded;
use futures::channel::oneshot::{channel, Receiver};
use futures::future::{lazy, Future, FutureExt};
use futures_channel::mpsc::unbounded;
use futures_channel::oneshot::{channel, Receiver};
use futures_util::future::{lazy, Future, FutureExt};
use tokio::task::LocalSet;
use crate::arbiter::{Arbiter, SystemArbiter};

View File

@@ -25,7 +25,7 @@ pub use actix_threadpool as blocking;
/// This function panics if actix system is not running.
pub fn spawn<F>(f: F)
where
F: futures::Future<Output = ()> + 'static,
F: futures_util::future::Future<Output = ()> + 'static,
{
if !System::is_set() {
panic!("System is not running");

View File

@@ -86,7 +86,6 @@ impl Runtime {
where
F: Future + 'static,
{
let res = self.local.block_on(&mut self.rt, f);
res
self.local.block_on(&mut self.rt, f)
}
}

View File

@@ -3,7 +3,7 @@ use std::future::Future;
use std::io;
use std::sync::atomic::{AtomicUsize, Ordering};
use futures::channel::mpsc::UnboundedSender;
use futures_channel::mpsc::UnboundedSender;
use tokio::task::LocalSet;
use crate::arbiter::{Arbiter, SystemCommand};
@@ -79,8 +79,8 @@ impl System {
})
}
/// Set current running system.
pub(crate) fn is_set() -> bool {
/// Check if current system is set, i.e., as already been started.
pub fn is_set() -> bool {
CURRENT.with(|cell| cell.borrow().is_some())
}

View File

@@ -0,0 +1,114 @@
use std::time::{Duration, Instant};
#[test]
fn start_and_stop() {
actix_rt::System::new("start_and_stop").block_on(async move {
assert!(
actix_rt::Arbiter::is_running(),
"System doesn't seem to have started"
);
});
assert!(
!actix_rt::Arbiter::is_running(),
"System doesn't seem to have stopped"
);
}
#[test]
fn await_for_timer() {
let time = Duration::from_secs(2);
let instant = Instant::now();
actix_rt::System::new("test_wait_timer").block_on(async move {
tokio::time::delay_for(time).await;
});
assert!(
instant.elapsed() >= time,
"Block on should poll awaited future to completion"
);
}
#[test]
fn join_another_arbiter() {
let time = Duration::from_secs(2);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.send(Box::pin(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
}));
arbiter.join().unwrap();
});
assert!(
instant.elapsed() >= time,
"Join on another arbiter should complete only when it calls stop"
);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.exec_fn(move || {
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
});
arbiter.join().unwrap();
});
assert!(
instant.elapsed() >= time,
"Join on a arbiter that has used actix_rt::spawn should wait for said future"
);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.send(Box::pin(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
}));
arbiter.stop();
arbiter.join().unwrap();
});
assert!(
instant.elapsed() < time,
"Premature stop of arbiter should conclude regardless of it's current state"
);
}
#[test]
fn join_current_arbiter() {
let time = Duration::from_secs(2);
let instant = Instant::now();
actix_rt::System::new("test_join_current_arbiter").block_on(async move {
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
actix_rt::Arbiter::local_join().await;
});
assert!(
instant.elapsed() >= time,
"Join on current arbiter should wait for all spawned futures"
);
let large_timer = Duration::from_secs(20);
let instant = Instant::now();
actix_rt::System::new("test_join_current_arbiter").block_on(async move {
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
let f = actix_rt::Arbiter::local_join();
actix_rt::spawn(async move {
tokio::time::delay_for(large_timer).await;
actix_rt::Arbiter::current().stop();
});
f.await;
});
assert!(
instant.elapsed() < large_timer,
"local_join should await only for the already spawned futures"
);
}

View File

@@ -1,5 +1,27 @@
# Changes
## Unreleased
### Changed
* workers must be greater than 0
## [1.0.3] - 2020-05-19
### Changed
* Replace deprecated `net2` crate with `socket2` [#140]
[#140]: https://github.com/actix/actix-net/pull/140
## [1.0.2] - 2020-02-26
### Fixed
* Avoid error by calling `reregister()` on Windows [#103]
[#103]: https://github.com/actix/actix-net/pull/103
## [1.0.1] - 2019-12-29
### Changed

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-server"
version = "1.0.1"
version = "1.0.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix server - General purpose tcp server"
keywords = ["network", "framework", "async", "futures"]
@@ -8,8 +8,8 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-server/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
license = "MIT OR Apache-2.0"
exclude = [".gitignore", ".cargo/config"]
edition = "2018"
workspace = ".."
@@ -23,20 +23,22 @@ default = []
[dependencies]
actix-service = "1.0.1"
actix-rt = "1.0.0"
actix-codec = "0.2.0"
actix-utils = "1.0.4"
actix-codec = "0.3.0"
actix-utils = "2.0.0"
log = "0.4"
num_cpus = "1.11"
mio = "0.6.19"
net2 = "0.2"
futures = "0.3.1"
socket2 = "0.3"
futures-channel = { version = "0.3.4", default-features = false }
futures-util = { version = "0.3.4", default-features = false, features = ["sink"] }
slab = "0.4"
# unix domain sockets
# FIXME: Remove it and use mio own uds feature once mio 0.7 is released
mio-uds = { version = "0.6.7" }
[dev-dependencies]
bytes = "0.5"
env_logger = "0.7"
actix-testing = "1.0.0"
actix-testing = "1.0.0"

View File

@@ -298,12 +298,7 @@ impl Accept {
}
Command::Resume => {
for (token, info) in self.sockets.iter() {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
if let Err(err) = self.register(token, info) {
error!("Can not resume socket accept process: {}", err);
} else {
info!(
@@ -338,17 +333,44 @@ impl Accept {
true
}
#[cfg(not(target_os = "windows"))]
fn register(&self, token: usize, info: &ServerSocketInfo) -> io::Result<()> {
self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
}
#[cfg(target_os = "windows")]
fn register(&self, token: usize, info: &ServerSocketInfo) -> io::Result<()> {
// On windows, calling register without deregister cause an error.
// See https://github.com/actix/actix-web/issues/905
// Calling reregister seems to fix the issue.
self.poll
.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
.or_else(|_| {
self.poll.reregister(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
})
}
fn backpressure(&mut self, on: bool) {
if self.backpressure {
if !on {
self.backpressure = false;
for (token, info) in self.sockets.iter() {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
if let Err(err) = self.register(token, info) {
error!("Can not resume socket accept process: {}", err);
} else {
info!("Accepting connections on {} has been resumed", info.addr);

View File

@@ -6,14 +6,13 @@ use std::{io, mem, net};
use actix_rt::net::TcpStream;
use actix_rt::time::{delay_until, Instant};
use actix_rt::{spawn, System};
use futures::channel::mpsc::{unbounded, UnboundedReceiver};
use futures::channel::oneshot;
use futures::future::ready;
use futures::stream::FuturesUnordered;
use futures::{ready, Future, FutureExt, Stream, StreamExt};
use futures_channel::mpsc::{unbounded, UnboundedReceiver};
use futures_channel::oneshot;
use futures_util::future::ready;
use futures_util::stream::FuturesUnordered;
use futures_util::{future::Future, ready, stream::Stream, FutureExt, StreamExt};
use log::{error, info};
use net2::TcpBuilder;
use num_cpus;
use socket2::{Domain, Protocol, Socket, Type};
use crate::accept::{AcceptLoop, AcceptNotify, Command};
use crate::config::{ConfiguredService, ServiceConfig};
@@ -73,8 +72,9 @@ impl ServerBuilder {
/// Set number of workers to start.
///
/// By default server uses number of available logical cpu as workers
/// count.
/// count. Workers must be greater than 0.
pub fn workers(mut self, num: usize) -> Self {
assert_ne!(num, 0, "workers must be greater than 0");
self.threads = num;
self
}
@@ -220,7 +220,7 @@ impl ServerBuilder {
self.services.push(StreamNewService::create(
name.as_ref().to_string(),
token,
factory.clone(),
factory,
addr,
));
self.sockets
@@ -263,19 +263,21 @@ impl ServerBuilder {
info!("Starting {} workers", self.threads);
// start workers
let mut workers = Vec::new();
for idx in 0..self.threads {
let worker = self.start_worker(idx, self.accept.get_notify());
workers.push(worker.clone());
self.workers.push((idx, worker));
}
let workers = (0..self.threads)
.map(|idx| {
let worker = self.start_worker(idx, self.accept.get_notify());
self.workers.push((idx, worker.clone()));
worker
})
.collect();
// start accept thread
for sock in &self.sockets {
info!("Starting \"{}\" service on {}", sock.1, sock.2);
}
self.accept.start(
mem::replace(&mut self.sockets, Vec::new())
mem::take(&mut self.sockets)
.into_iter()
.map(|t| (t.0, t.2))
.collect(),
@@ -354,7 +356,7 @@ impl ServerBuilder {
// stop accept thread
self.accept.send(Command::Stop);
let notify = std::mem::replace(&mut self.notify, Vec::new());
let notify = std::mem::take(&mut self.notify);
// stop workers
if !self.workers.is_empty() && graceful {
@@ -486,11 +488,13 @@ pub(super) fn bind_addr<S: net::ToSocketAddrs>(
}
fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result<net::TcpListener> {
let builder = match addr {
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
let domain = match addr {
net::SocketAddr::V4(_) => Domain::ipv4(),
net::SocketAddr::V6(_) => Domain::ipv6(),
};
builder.reuse_address(true)?;
builder.bind(addr)?;
Ok(builder.listen(backlog)?)
let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?;
socket.set_reuse_address(true)?;
socket.bind(&addr.into())?;
socket.listen(backlog)?;
Ok(socket.into_tcp_listener())
}

View File

@@ -4,7 +4,7 @@ use std::{fmt, io, net};
use actix_rt::net::TcpStream;
use actix_service as actix;
use actix_utils::counter::CounterGuard;
use futures::future::{ok, Future, FutureExt, LocalBoxFuture};
use futures_util::future::{ok, Future, FutureExt, LocalBoxFuture};
use log::error;
use super::builder::bind_addr;
@@ -91,7 +91,7 @@ impl ConfiguredService {
pub(super) fn stream(&mut self, token: Token, name: String, addr: net::SocketAddr) {
self.names.insert(token, (name.clone(), addr));
self.topics.insert(name.clone(), token);
self.topics.insert(name, token);
self.services.push(token);
}
}
@@ -218,7 +218,7 @@ impl ServiceRuntime {
// let name = name.to_owned();
if let Some(token) = self.names.get(name) {
self.services.insert(
token.clone(),
*token,
Box::new(ServiceFactory {
inner: service.into_factory(),
}),

View File

@@ -3,9 +3,9 @@ use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures::channel::mpsc::UnboundedSender;
use futures::channel::oneshot;
use futures::FutureExt;
use futures_channel::mpsc::UnboundedSender;
use futures_channel::oneshot;
use futures_util::FutureExt;
use crate::builder::ServerBuilder;
use crate::signals::Signal;

View File

@@ -6,8 +6,8 @@ use std::time::Duration;
use actix_rt::spawn;
use actix_service::{self as actix, Service, ServiceFactory as ActixServiceFactory};
use actix_utils::counter::CounterGuard;
use futures::future::{err, ok, LocalBoxFuture, Ready};
use futures::{FutureExt, TryFutureExt};
use futures_util::future::{err, ok, LocalBoxFuture, Ready};
use futures_util::{FutureExt, TryFutureExt};
use log::error;
use super::Token;

View File

@@ -3,7 +3,7 @@ use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures::future::lazy;
use futures_util::future::lazy;
use crate::server::Server;

View File

@@ -7,10 +7,10 @@ use std::time;
use actix_rt::time::{delay_until, Delay, Instant};
use actix_rt::{spawn, Arbiter};
use actix_utils::counter::Counter;
use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures::channel::oneshot;
use futures::future::{join_all, LocalBoxFuture, MapOk};
use futures::{Future, FutureExt, Stream, TryFutureExt};
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_channel::oneshot;
use futures_util::future::{join_all, LocalBoxFuture, MapOk};
use futures_util::{future::Future, stream::Stream, FutureExt, TryFutureExt};
use log::{error, info, trace};
use crate::accept::AcceptNotify;
@@ -315,6 +315,8 @@ enum WorkerState {
impl Future for Worker {
type Output = ();
// FIXME: remove this attribute
#[allow(clippy::never_loop)]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// `StopWorker` message handler
if let Poll::Ready(Some(StopCommand { graceful, result })) =
@@ -368,11 +370,8 @@ impl Future for Worker {
Ok(false) => {
// push connection back to queue
if let Some(conn) = conn {
match self.state {
WorkerState::Unavailable(ref mut conns) => {
conns.push(conn);
}
_ => (),
if let WorkerState::Unavailable(ref mut conns) = self.state {
conns.push(conn);
}
}
Poll::Pending

View File

@@ -1,23 +1,18 @@
use std::io::Read;
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use std::sync::{mpsc, Arc};
use std::{net, thread, time};
use actix_codec::{BytesCodec, Framed};
use actix_rt::net::TcpStream;
use actix_server::Server;
use actix_service::fn_service;
use bytes::Bytes;
use futures::future::{lazy, ok};
use futures::SinkExt;
use net2::TcpBuilder;
use futures_util::future::{lazy, ok};
use socket2::{Domain, Protocol, Socket, Type};
fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = TcpBuilder::new_v4().unwrap();
socket.bind(&addr).unwrap();
socket.reuse_address(true).unwrap();
let tcp = socket.to_tcp_listener().unwrap();
let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
socket.bind(&addr.into()).unwrap();
socket.set_reuse_address(true).unwrap();
let tcp = socket.into_tcp_listener();
tcp.local_addr().unwrap()
}
@@ -41,7 +36,7 @@ fn test_bind() {
thread::sleep(time::Duration::from_millis(500));
assert!(net::TcpStream::connect(addr).is_ok());
let _ = sys.stop();
sys.stop();
let _ = h.join();
}
@@ -66,13 +61,19 @@ fn test_listen() {
thread::sleep(time::Duration::from_millis(500));
assert!(net::TcpStream::connect(addr).is_ok());
let _ = sys.stop();
sys.stop();
let _ = h.join();
}
#[test]
#[cfg(unix)]
fn test_start() {
use actix_codec::{BytesCodec, Framed};
use actix_rt::net::TcpStream;
use bytes::Bytes;
use futures_util::sink::SinkExt;
use std::io::Read;
let addr = unused_addr();
let (tx, rx) = mpsc::channel();
@@ -82,12 +83,10 @@ fn test_start() {
.backlog(100)
.disable_signals()
.bind("test", addr, move || {
fn_service(|io: TcpStream| {
async move {
let mut f = Framed::new(io, BytesCodec);
f.send(Bytes::from_static(b"test")).await.unwrap();
Ok::<_, ()>(())
}
fn_service(|io: TcpStream| async move {
let mut f = Framed::new(io, BytesCodec);
f.send(Bytes::from_static(b"test")).await.unwrap();
Ok::<_, ()>(())
})
})
.unwrap()
@@ -130,7 +129,7 @@ fn test_start() {
assert!(net::TcpStream::connect(addr).is_err());
thread::sleep(time::Duration::from_millis(100));
let _ = sys.stop();
sys.stop();
let _ = h.join();
}
@@ -178,6 +177,6 @@ fn test_configure() {
assert!(net::TcpStream::connect(addr2).is_ok());
assert!(net::TcpStream::connect(addr3).is_ok());
assert_eq!(num.load(Relaxed), 1);
let _ = sys.stop();
sys.stop();
let _ = h.join();
}

View File

@@ -1,5 +1,32 @@
# Changes
## Unreleased - 2020-xx-xx
## 1.0.6 - 2020-08-09
### Fixed
* Removed unsound custom Cell implementation that allowed obtaining several mutable references to the same data, which is undefined behavior in Rust and could lead to violations of memory safety. External code could obtain several mutable references to the same data through service combinators. Attempts to acquire several mutable references to the same data will instead result in a panic.
## [1.0.5] - 2020-01-16
### Fixed
* Fixed unsoundness in .and_then()/.then() service combinators
## [1.0.4] - 2020-01-15
### Fixed
* Revert 1.0.3 change
## [1.0.3] - 2020-01-15
### Fixed
* Fixed unsoundness in `AndThenService` impl
## [1.0.2] - 2020-01-08
### Added

View File

@@ -1,28 +1,33 @@
[package]
name = "actix-service"
version = "1.0.2"
version = "1.0.6"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix service"
keywords = ["network", "framework", "async", "futures"]
description = "Service trait and combinators for representing asynchronous request/response operations."
keywords = ["network", "framework", "async", "futures", "service"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-service/"
documentation = "https://docs.rs/actix-service"
readme = "README.md"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
edition = "2018"
[badges]
travis-ci = { repository = "actix/actix-service", branch = "master" }
appveyor = { repository = "actix/actix-net" }
codecov = { repository = "actix/actix-service", branch = "master", service = "github" }
[lib]
name = "actix_service"
path = "src/lib.rs"
[dependencies]
futures-util = "0.3.1"
pin-project = "0.4.6"
pin-project = "0.4.17"
[dev-dependencies]
actix-rt = "1.0.0"
criterion = "0.3"
[[bench]]
name = "unsafecell_vs_refcell"
harness = false
[[bench]]
name = "and_then"
harness = false

7
actix-service/README.md Normal file
View File

@@ -0,0 +1,7 @@
# actix-service
> Service trait and combinators for representing asynchronous request/response operations.
See documentation for detailed explanations these components: [https://docs.rs/actix-service](docs).
[docs]: https://docs.rs/actix-service

View File

@@ -0,0 +1,332 @@
use actix_service::boxed::BoxFuture;
use actix_service::IntoService;
use actix_service::Service;
/// Benchmark various implementations of and_then
use criterion::{criterion_main, Criterion};
use futures_util::future::join_all;
use futures_util::future::TryFutureExt;
use std::cell::{RefCell, UnsafeCell};
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
/*
* Test services A,B for AndThen service implementations
*/
async fn svc1(_: ()) -> Result<usize, ()> {
Ok(1)
}
async fn svc2(req: usize) -> Result<usize, ()> {
Ok(req + 1)
}
/*
* AndThenUC - original AndThen service based on UnsafeCell
* Cut down version of actix_service::AndThenService based on actix-service::Cell
*/
struct AndThenUC<A, B>(Rc<UnsafeCell<(A, B)>>);
impl<A, B> AndThenUC<A, B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(UnsafeCell::new((a, b))))
}
}
impl<A, B> Clone for AndThenUC<A, B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A, B> Service for AndThenUC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = AndThenServiceResponse<A, B>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = unsafe { &mut *(*self.0).get() }.0.call(req);
AndThenServiceResponse {
state: State::A(fut, Some(self.0.clone())),
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
#[pin]
state: State<A, B>,
}
#[pin_project::pin_project(project = StateProj)]
enum State<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
A(#[pin] A::Future, Option<Rc<UnsafeCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for AndThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Output = Result<B::Response, A::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
StateProj::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let b = b.take().unwrap();
this.state.set(State::Empty); // drop fut A
let fut = unsafe { &mut (*b.get()).1 }.call(res);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/*
* AndThenRC - AndThen service based on RefCell
*/
struct AndThenRC<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B> AndThenRC<A, B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(RefCell::new((a, b))))
}
}
impl<A, B> Clone for AndThenRC<A, B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A, B> Service for AndThenRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = AndThenServiceResponseRC<A, B>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.0.borrow_mut().0.call(req);
AndThenServiceResponseRC {
state: StateRC::A(fut, Some(self.0.clone())),
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenServiceResponseRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
#[pin]
state: StateRC<A, B>,
}
#[pin_project::pin_project(project = StateRCProj)]
enum StateRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for AndThenServiceResponseRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Output = Result<B::Response, A::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
StateRCProj::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let b = b.take().unwrap();
this.state.set(StateRC::Empty); // drop fut A
let fut = b.borrow_mut().1.call(res);
this.state.set(StateRC::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateRCProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(StateRC::Empty);
r
}),
StateRCProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/*
* AndThenRCFuture - AndThen service based on RefCell
* and standard futures::future::and_then combinator in a Box
*/
struct AndThenRCFuture<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B> AndThenRCFuture<A, B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(RefCell::new((a, b))))
}
}
impl<A, B> Clone for AndThenRCFuture<A, B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A, B> Service for AndThenRCFuture<A, B>
where
A: Service + 'static,
A::Future: 'static,
B: Service<Request = A::Response, Error = A::Error> + 'static,
B::Future: 'static,
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.0.borrow_mut().0.call(req);
let core = self.0.clone();
let fut2 = move |res| (*core).borrow_mut().1.call(res);
Box::pin(fut.and_then(fut2))
}
}
/// Criterion Benchmark for async Service
/// Should be used from within criterion group:
/// ```rust,ignore
/// let mut criterion: ::criterion::Criterion<_> =
/// ::criterion::Criterion::default().configure_from_args();
/// bench_async_service(&mut criterion, ok_service(), "async_service_direct");
/// ```
///
/// Usable for benching Service wrappers:
/// Using minimum service code implementation we first measure
/// time to run minimum service, then measure time with wrapper.
///
/// Sample output
/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us]
pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
where
S: Service<Request = (), Response = usize, Error = ()> + Clone + 'static,
{
let mut rt = actix_rt::System::new("test");
// start benchmark loops
c.bench_function(name, move |b| {
b.iter_custom(|iters| {
let mut srvs: Vec<_> = (1..iters).map(|_| srv.clone()).collect();
// exclude request generation, it appears it takes significant time vs call (3us vs 1us)
let start = std::time::Instant::now();
// benchmark body
rt.block_on(async move { join_all(srvs.iter_mut().map(|srv| srv.call(()))).await });
// check that at least first request succeeded
start.elapsed()
})
});
}
pub fn service_benches() {
let mut criterion: ::criterion::Criterion<_> =
::criterion::Criterion::default().configure_from_args();
bench_async_service(
&mut criterion,
AndThenUC::new(svc1.into_service(), svc2.into_service()),
"AndThen with UnsafeCell",
);
bench_async_service(
&mut criterion,
AndThenRC::new(svc1.into_service(), svc2.into_service()),
"AndThen with RefCell",
);
bench_async_service(
&mut criterion,
AndThenUC::new(svc1.into_service(), svc2.into_service()),
"AndThen with UnsafeCell",
);
bench_async_service(
&mut criterion,
AndThenRC::new(svc1.into_service(), svc2.into_service()),
"AndThen with RefCell",
);
bench_async_service(
&mut criterion,
AndThenRCFuture::new(svc1.into_service(), svc2.into_service()),
"AndThen with RefCell via future::and_then",
);
}
criterion_main!(service_benches);

View File

@@ -0,0 +1,112 @@
use actix_service::Service;
use criterion::{criterion_main, Criterion};
use futures_util::future::join_all;
use futures_util::future::{ok, Ready};
use std::cell::{RefCell, UnsafeCell};
use std::rc::Rc;
use std::task::{Context, Poll};
struct SrvUC(Rc<UnsafeCell<usize>>);
impl Default for SrvUC {
fn default() -> Self {
Self(Rc::new(UnsafeCell::new(0)))
}
}
impl Clone for SrvUC {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Service for SrvUC {
type Request = ();
type Response = usize;
type Error = ();
type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: ()) -> Self::Future {
unsafe { *(*self.0).get() = *(*self.0).get() + 1 };
ok(unsafe { *self.0.get() })
}
}
struct SrvRC(Rc<RefCell<usize>>);
impl Default for SrvRC {
fn default() -> Self {
Self(Rc::new(RefCell::new(0)))
}
}
impl Clone for SrvRC {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Service for SrvRC {
type Request = ();
type Response = usize;
type Error = ();
type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: ()) -> Self::Future {
let prev = *self.0.borrow();
*(*self.0).borrow_mut() = prev + 1;
ok(*self.0.borrow())
}
}
/// Criterion Benchmark for async Service
/// Should be used from within criterion group:
/// ```rust,ignore
/// let mut criterion: ::criterion::Criterion<_> =
/// ::criterion::Criterion::default().configure_from_args();
/// bench_async_service(&mut criterion, ok_service(), "async_service_direct");
/// ```
///
/// Usable for benching Service wrappers:
/// Using minimum service code implementation we first measure
/// time to run minimum service, then measure time with wrapper.
///
/// Sample output
/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us]
pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
where
S: Service<Request = (), Response = usize, Error = ()> + Clone + 'static,
{
let mut rt = actix_rt::System::new("test");
// start benchmark loops
c.bench_function(name, move |b| {
b.iter_custom(|iters| {
let mut srvs: Vec<_> = (1..iters).map(|_| srv.clone()).collect();
// exclude request generation, it appears it takes significant time vs call (3us vs 1us)
let start = std::time::Instant::now();
// benchmark body
rt.block_on(async move { join_all(srvs.iter_mut().map(|srv| srv.call(()))).await });
// check that at least first request succeeded
start.elapsed()
})
});
}
pub fn service_benches() {
let mut criterion: ::criterion::Criterion<_> =
::criterion::Criterion::default().configure_from_args();
bench_async_service(&mut criterion, SrvUC::default(), "Service with UnsafeCell");
bench_async_service(&mut criterion, SrvRC::default(), "Service with RefCell");
bench_async_service(&mut criterion, SrvUC::default(), "Service with UnsafeCell");
bench_async_service(&mut criterion, SrvRC::default(), "Service with RefCell");
}
criterion_main!(service_benches);

View File

@@ -1,15 +1,16 @@
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use super::{Service, ServiceFactory};
use crate::cell::Cell;
/// Service for the `and_then` combinator, chaining a computation onto the end
/// of another service which completes successfully.
///
/// This is created by the `ServiceExt::and_then` method.
pub struct AndThenService<A, B>(Cell<(A, B)>);
pub(crate) struct AndThenService<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B> AndThenService<A, B> {
/// Create new `AndThen` combinator
@@ -18,7 +19,7 @@ impl<A, B> AndThenService<A, B> {
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Cell::new((a, b)))
Self(Rc::new(RefCell::new((a, b))))
}
}
@@ -39,8 +40,7 @@ where
type Future = AndThenServiceResponse<A, B>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let srv = self.0.get_mut();
let mut srv = self.0.borrow_mut();
let not_ready = !srv.0.poll_ready(cx)?.is_ready();
if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
Poll::Pending
@@ -51,13 +51,13 @@ where
fn call(&mut self, req: A::Request) -> Self::Future {
AndThenServiceResponse {
state: State::A(self.0.get_mut().0.call(req), Some(self.0.clone())),
state: State::A(self.0.borrow_mut().0.call(req), Some(self.0.clone())),
}
}
}
#[pin_project::pin_project]
pub struct AndThenServiceResponse<A, B>
pub(crate) struct AndThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
@@ -66,13 +66,13 @@ where
state: State<A, B>,
}
#[pin_project::pin_project]
#[pin_project::pin_project(project = StateProj)]
enum State<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
A(#[pin] A::Future, Option<Cell<(A, B)>>),
A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
@@ -84,33 +84,33 @@ where
{
type Output = Result<B::Response, A::Error>;
#[pin_project::project]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
#[project]
match this.state.as_mut().project() {
State::A(fut, b) => match fut.poll(cx)? {
StateProj::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let mut b = b.take().unwrap();
let b = b.take().unwrap();
this.state.set(State::Empty); // drop fut A
let fut = b.get_mut().1.call(res);
let fut = b.borrow_mut().1.call(res);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
State::B(fut) => fut.poll(cx).map(|r| {
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
State::Empty => panic!("future must not be polled after it returned `Poll::Ready`"),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/// `.and_then()` service factory combinator
pub struct AndThenServiceFactory<A, B>
pub(crate) struct AndThenServiceFactory<A, B>
where
A: ServiceFactory,
A::Config: Clone,
@@ -121,8 +121,7 @@ where
InitError = A::InitError,
>,
{
a: A,
b: B,
inner: Rc<(A, B)>,
}
impl<A, B> AndThenServiceFactory<A, B>
@@ -138,7 +137,9 @@ where
{
/// Create new `AndThenFactory` combinator
pub(crate) fn new(a: A, b: B) -> Self {
Self { a, b }
Self {
inner: Rc::new((a, b)),
}
}
}
@@ -163,34 +164,34 @@ where
type Future = AndThenServiceFactoryResponse<A, B>;
fn new_service(&self, cfg: A::Config) -> Self::Future {
let inner = &*self.inner;
AndThenServiceFactoryResponse::new(
self.a.new_service(cfg.clone()),
self.b.new_service(cfg),
inner.0.new_service(cfg.clone()),
inner.1.new_service(cfg),
)
}
}
impl<A, B> Clone for AndThenServiceFactory<A, B>
where
A: ServiceFactory + Clone,
A: ServiceFactory,
A::Config: Clone,
B: ServiceFactory<
Config = A::Config,
Request = A::Response,
Error = A::Error,
InitError = A::InitError,
> + Clone,
Config = A::Config,
Request = A::Response,
Error = A::Error,
InitError = A::InitError,
>,
{
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
b: self.b.clone(),
inner: self.inner.clone(),
}
}
}
#[pin_project::pin_project]
pub struct AndThenServiceFactoryResponse<A, B>
pub(crate) struct AndThenServiceFactoryResponse<A, B>
where
A: ServiceFactory,
B: ServiceFactory<Request = A::Response>,
@@ -312,7 +313,7 @@ mod tests {
let mut srv = pipeline(Srv1(cnt.clone())).and_then(Srv2(cnt));
let res = srv.call("srv1").await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv1", "srv2")));
assert_eq!(res.unwrap(), ("srv1", "srv2"));
}
#[actix_rt::test]

View File

@@ -1,13 +1,14 @@
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use crate::cell::Cell;
use crate::{Service, ServiceFactory};
/// `Apply` service combinator
pub struct AndThenApplyFn<A, B, F, Fut, Res, Err>
pub(crate) struct AndThenApplyFn<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
@@ -15,8 +16,7 @@ where
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
a: A,
b: Cell<(B, F)>,
srv: Rc<RefCell<(A, B, F)>>,
r: PhantomData<(Fut, Res, Err)>,
}
@@ -31,8 +31,7 @@ where
/// Create new `Apply` combinator
pub(crate) fn new(a: A, b: B, f: F) -> Self {
Self {
a,
b: Cell::new((b, f)),
srv: Rc::new(RefCell::new((a, b, f))),
r: PhantomData,
}
}
@@ -40,7 +39,7 @@ where
impl<A, B, F, Fut, Res, Err> Clone for AndThenApplyFn<A, B, F, Fut, Res, Err>
where
A: Service + Clone,
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
@@ -48,8 +47,7 @@ where
{
fn clone(&self) -> Self {
AndThenApplyFn {
a: self.a.clone(),
b: self.b.clone(),
srv: self.srv.clone(),
r: PhantomData,
}
}
@@ -69,8 +67,9 @@ where
type Future = AndThenApplyFnFuture<A, B, F, Fut, Res, Err>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let not_ready = self.a.poll_ready(cx)?.is_pending();
if self.b.get_mut().0.poll_ready(cx)?.is_pending() || not_ready {
let mut inner = self.srv.borrow_mut();
let not_ready = inner.0.poll_ready(cx)?.is_pending();
if inner.1.poll_ready(cx)?.is_pending() || not_ready {
Poll::Pending
} else {
Poll::Ready(Ok(()))
@@ -78,14 +77,15 @@ where
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.srv.borrow_mut().0.call(req);
AndThenApplyFnFuture {
state: State::A(self.a.call(req), Some(self.b.clone())),
state: State::A(fut, Some(self.srv.clone())),
}
}
}
#[pin_project::pin_project]
pub struct AndThenApplyFnFuture<A, B, F, Fut, Res, Err>
pub(crate) struct AndThenApplyFnFuture<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
@@ -98,7 +98,7 @@ where
state: State<A, B, F, Fut, Res, Err>,
}
#[pin_project::pin_project]
#[pin_project::pin_project(project = StateProj)]
enum State<A, B, F, Fut, Res, Err>
where
A: Service,
@@ -108,7 +108,7 @@ where
Err: From<A::Error>,
Err: From<B::Error>,
{
A(#[pin] A::Future, Option<Cell<(B, F)>>),
A(#[pin] A::Future, Option<Rc<RefCell<(A, B, F)>>>),
B(#[pin] Fut),
Empty,
}
@@ -123,37 +123,35 @@ where
{
type Output = Result<Res, Err>;
#[pin_project::project]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
#[project]
match this.state.as_mut().project() {
State::A(fut, b) => match fut.poll(cx)? {
StateProj::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let mut b = b.take().unwrap();
let b = b.take().unwrap();
this.state.set(State::Empty);
let b = b.get_mut();
let fut = (&mut b.1)(res, &mut b.0);
let (_, b, f) = &mut *b.borrow_mut();
let fut = f(res, b);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
State::B(fut) => fut.poll(cx).map(|r| {
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
State::Empty => panic!("future must not be polled after it returned `Poll::Ready`"),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/// `AndThenApplyFn` service factory
pub struct AndThenApplyFnFactory<A, B, F, Fut, Res, Err> {
a: A,
b: B,
f: F,
pub(crate) struct AndThenApplyFnFactory<A, B, F, Fut, Res, Err> {
srv: Rc<(A, B, F)>,
r: PhantomData<(Fut, Res, Err)>,
}
@@ -168,25 +166,16 @@ where
/// Create new `ApplyNewService` new service instance
pub(crate) fn new(a: A, b: B, f: F) -> Self {
Self {
a: a,
b: b,
f: f,
srv: Rc::new((a, b, f)),
r: PhantomData,
}
}
}
impl<A, B, F, Fut, Res, Err> Clone for AndThenApplyFnFactory<A, B, F, Fut, Res, Err>
where
A: Clone,
B: Clone,
F: Clone,
{
impl<A, B, F, Fut, Res, Err> Clone for AndThenApplyFnFactory<A, B, F, Fut, Res, Err> {
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
b: self.b.clone(),
f: self.f.clone(),
srv: self.srv.clone(),
r: PhantomData,
}
}
@@ -210,18 +199,19 @@ where
type Future = AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err>;
fn new_service(&self, cfg: A::Config) -> Self::Future {
let srv = &*self.srv;
AndThenApplyFnFactoryResponse {
a: None,
b: None,
f: self.f.clone(),
fut_a: self.a.new_service(cfg.clone()),
fut_b: self.b.new_service(cfg),
f: srv.2.clone(),
fut_a: srv.0.new_service(cfg.clone()),
fut_b: srv.1.new_service(cfg),
}
}
}
#[pin_project::pin_project]
pub struct AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err>
pub(crate) struct AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err>
where
A: ServiceFactory,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>,
@@ -267,8 +257,11 @@ where
if this.a.is_some() && this.b.is_some() {
Poll::Ready(Ok(AndThenApplyFn {
a: this.a.take().unwrap(),
b: Cell::new((this.b.take().unwrap(), this.f.clone())),
srv: Rc::new(RefCell::new((
this.a.take().unwrap(),
this.b.take().unwrap(),
this.f.clone(),
))),
r: PhantomData,
}))
} else {
@@ -297,6 +290,7 @@ mod tests {
Poll::Ready(Ok(()))
}
#[allow(clippy::unit_arg)]
fn call(&mut self, req: Self::Request) -> Self::Future {
ok(req)
}
@@ -304,10 +298,9 @@ mod tests {
#[actix_rt::test]
async fn test_service() {
let mut srv = pipeline(|r: &'static str| ok(r))
.and_then_apply_fn(Srv, |req: &'static str, s| {
s.call(()).map_ok(move |res| (req, res))
});
let mut srv = pipeline(ok).and_then_apply_fn(Srv, |req: &'static str, s| {
s.call(()).map_ok(move |res| (req, res))
});
let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Ok(())));
@@ -318,11 +311,10 @@ mod tests {
#[actix_rt::test]
async fn test_service_factory() {
let new_srv = pipeline_factory(|| ok::<_, ()>(fn_service(|r: &'static str| ok(r))))
.and_then_apply_fn(
|| ok(Srv),
|req: &'static str, s| s.call(()).map_ok(move |res| (req, res)),
);
let new_srv = pipeline_factory(|| ok::<_, ()>(fn_service(ok))).and_then_apply_fn(
|| ok(Srv),
|req: &'static str, s| s.call(()).map_ok(move |res| (req, res)),
);
let mut srv = new_srv.new_service(()).await.unwrap();
let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Ok(())));

View File

@@ -233,8 +233,8 @@ mod tests {
let mut srv = pipeline(apply_fn(Srv, |req: &'static str, srv| {
let fut = srv.call(());
async move {
let res = fut.await.unwrap();
Ok((req, res))
fut.await.unwrap();
Ok((req, ()))
}
}));
@@ -242,7 +242,7 @@ mod tests {
let res = srv.call("srv").await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv", ())));
assert_eq!(res.unwrap(), ("srv", ()));
}
#[actix_rt::test]
@@ -252,8 +252,8 @@ mod tests {
|req: &'static str, srv| {
let fut = srv.call(());
async move {
let res = fut.await.unwrap();
Ok((req, res))
fut.await.unwrap();
Ok((req, ()))
}
},
));
@@ -264,6 +264,6 @@ mod tests {
let res = srv.call("srv").await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv", ())));
assert_eq!(res.unwrap(), ("srv", ()));
}
}

View File

@@ -1,13 +1,25 @@
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use crate::cell::Cell;
use crate::{Service, ServiceFactory};
/// Convert `Fn(Config, &mut Service1) -> Future<Service2>` fn to a service factory
pub fn apply_cfg<F, C, T, R, S, E>(srv: T, f: F) -> ApplyConfigService<F, C, T, R, S, E>
pub fn apply_cfg<F, C, T, R, S, E>(
srv: T,
f: F,
) -> impl ServiceFactory<
Config = C,
Request = S::Request,
Response = S::Response,
Error = S::Error,
Service = S,
InitError = E,
Future = R,
> + Clone
where
F: FnMut(C, &mut T) -> R,
T: Service,
@@ -15,7 +27,7 @@ where
S: Service,
{
ApplyConfigService {
srv: Cell::new((srv, f)),
srv: Rc::new(RefCell::new((srv, f))),
_t: PhantomData,
}
}
@@ -26,7 +38,14 @@ where
pub fn apply_cfg_factory<F, C, T, R, S>(
factory: T,
f: F,
) -> ApplyConfigServiceFactory<F, C, T, R, S>
) -> impl ServiceFactory<
Config = C,
Request = S::Request,
Response = S::Response,
Error = S::Error,
Service = S,
InitError = T::InitError,
> + Clone
where
F: FnMut(C, &mut T::Service) -> R,
T: ServiceFactory<Config = ()>,
@@ -35,20 +54,20 @@ where
S: Service,
{
ApplyConfigServiceFactory {
srv: Cell::new((factory, f)),
srv: Rc::new(RefCell::new((factory, f))),
_t: PhantomData,
}
}
/// Convert `Fn(Config, &mut Server) -> Future<Service>` fn to NewService\
pub struct ApplyConfigService<F, C, T, R, S, E>
struct ApplyConfigService<F, C, T, R, S, E>
where
F: FnMut(C, &mut T) -> R,
T: Service,
R: Future<Output = Result<S, E>>,
S: Service,
{
srv: Cell<(T, F)>,
srv: Rc<RefCell<(T, F)>>,
_t: PhantomData<(C, R, S)>,
}
@@ -84,22 +103,20 @@ where
type Future = R;
fn new_service(&self, cfg: C) -> Self::Future {
unsafe {
let srv = self.srv.get_mut_unsafe();
(srv.1)(cfg, &mut srv.0)
}
let (t, f) = &mut *self.srv.borrow_mut();
f(cfg, t)
}
}
/// Convert `Fn(&Config) -> Future<Service>` fn to NewService
pub struct ApplyConfigServiceFactory<F, C, T, R, S>
struct ApplyConfigServiceFactory<F, C, T, R, S>
where
F: FnMut(C, &mut T::Service) -> R,
T: ServiceFactory<Config = ()>,
R: Future<Output = Result<S, T::InitError>>,
S: Service,
{
srv: Cell<(T, F)>,
srv: Rc<RefCell<(T, F)>>,
_t: PhantomData<(C, R, S)>,
}
@@ -139,13 +156,13 @@ where
ApplyConfigServiceFactoryResponse {
cfg: Some(cfg),
store: self.srv.clone(),
state: State::A(self.srv.get_ref().0.new_service(())),
state: State::A(self.srv.borrow().0.new_service(())),
}
}
}
#[pin_project::pin_project]
pub struct ApplyConfigServiceFactoryResponse<F, C, T, R, S>
struct ApplyConfigServiceFactoryResponse<F, C, T, R, S>
where
F: FnMut(C, &mut T::Service) -> R,
T: ServiceFactory<Config = ()>,
@@ -154,12 +171,12 @@ where
S: Service,
{
cfg: Option<C>,
store: Cell<(T, F)>,
store: Rc<RefCell<(T, F)>>,
#[pin]
state: State<T, R, S>,
}
#[pin_project::pin_project]
#[pin_project::pin_project(project = StateProj)]
enum State<T, R, S>
where
T: ServiceFactory<Config = ()>,
@@ -182,28 +199,29 @@ where
{
type Output = Result<S, T::InitError>;
#[pin_project::project]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
#[project]
match this.state.as_mut().project() {
State::A(fut) => match fut.poll(cx)? {
StateProj::A(fut) => match fut.poll(cx)? {
Poll::Pending => Poll::Pending,
Poll::Ready(srv) => {
this.state.set(State::B(srv));
self.poll(cx)
}
},
State::B(srv) => match srv.poll_ready(cx)? {
StateProj::B(srv) => match srv.poll_ready(cx)? {
Poll::Ready(_) => {
let fut = (this.store.get_mut().1)(this.cfg.take().unwrap(), srv);
this.state.set(State::C(fut));
{
let (_, f) = &mut *this.store.borrow_mut();
let fut = f(this.cfg.take().unwrap(), srv);
this.state.set(State::C(fut));
}
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
State::C(fut) => fut.poll(cx),
StateProj::C(fut) => fut.poll(cx),
}
}
}

View File

@@ -1,57 +0,0 @@
//! Custom cell impl, internal use only
use std::task::{Context, Poll};
use std::{cell::UnsafeCell, fmt, rc::Rc};
pub(crate) struct Cell<T> {
inner: Rc<UnsafeCell<T>>,
}
impl<T> Clone for Cell<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T: fmt::Debug> fmt::Debug for Cell<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
impl<T> Cell<T> {
pub(crate) fn new(inner: T) -> Self {
Self {
inner: Rc::new(UnsafeCell::new(inner)),
}
}
pub(crate) fn get_ref(&self) -> &T {
unsafe { &*self.inner.as_ref().get() }
}
pub(crate) fn get_mut(&mut self) -> &mut T {
unsafe { &mut *self.inner.as_ref().get() }
}
#[allow(clippy::mut_from_ref)]
pub(crate) unsafe fn get_mut_unsafe(&self) -> &mut T {
&mut *self.inner.as_ref().get()
}
}
impl<T: crate::Service> crate::Service for Cell<T> {
type Request = T::Request;
type Response = T::Response;
type Error = T::Error;
type Future = T::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.get_mut().poll_ready(cx)
}
fn call(&mut self, req: Self::Request) -> Self::Future {
self.get_mut().call(req)
}
}

View File

@@ -29,7 +29,7 @@ where
/// /// Service that divides two usize values.
/// async fn div((x, y): (usize, usize)) -> Result<usize, io::Error> {
/// if y == 0 {
/// Err(io::Error::new(io::ErrorKind::Other, "divide by zdro"))
/// Err(io::Error::new(io::ErrorKind::Other, "divide by zero"))
/// } else {
/// Ok(x / y)
/// }

View File

@@ -1,3 +1,5 @@
//! See [`Service`](trait.Service.html) docs for information on this crate's foundational trait.
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)]
@@ -12,7 +14,6 @@ mod and_then_apply_fn;
mod apply;
mod apply_cfg;
pub mod boxed;
mod cell;
mod fn_service;
mod map;
mod map_config;
@@ -30,21 +31,23 @@ pub use self::map_config::{map_config, unit_config};
pub use self::pipeline::{pipeline, pipeline_factory, Pipeline, PipelineFactory};
pub use self::transform::{apply, Transform};
/// An asynchronous function from `Request` to a `Response`.
/// An asynchronous operation from `Request` to a `Response`.
///
/// `Service` represents a service that represanting interation, taking requests and giving back
/// replies. You can think about service as a function with one argument and result as a return
/// type. In general form it looks like `async fn(Req) -> Result<Res, Err>`. `Service`
/// trait just generalizing form of this function. Each parameter described as an assotiated type.
/// The `Service` trait models a request/response interaction, receiving requests and returning
/// replies. You can think about a service as a function with one argument that returns some result
/// asynchronously. Conceptually, the operation looks like this:
///
/// Services provides a symmetric and uniform API, same abstractions represents
/// clients and servers. Services describe only `transforamtion` operation
/// which encorouge to simplify api surface and phrases `value transformation`.
/// That leads to simplier design of each service. That also allows better testability
/// and better composition.
/// ```rust,ignore
/// async fn(Request) -> Result<Response, Err>
/// ```
///
/// Services could be represented in several different forms. In general,
/// Service is a type that implements `Service` trait.
/// The `Service` trait just generalizes this form where each parameter is described as an
/// associated type on the trait. Services can also have mutable state that influence computation.
///
/// `Service` provides a symmetric and uniform API; the same abstractions can be used to represent
/// both clients and servers. Services describe only _transformation_ operations which encourage
/// simple API surfaces. This leads to simpler design of each service, improves test-ability and
/// makes composition easier.
///
/// ```rust,ignore
/// struct MyService;
@@ -53,16 +56,16 @@ pub use self::transform::{apply, Transform};
/// type Request = u8;
/// type Response = u64;
/// type Error = MyError;
/// type Future = Pin<Box<Future<Output=Result<Self::Response, Self::Error>>>;
/// type Future = Pin<Box<Future<Output=Result<Self::Response, Self::Error>>>>;
///
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... }
///
/// fn call(&mut self) -> Self::Future { ... }
/// fn call(&mut self, req: Self::Request) -> Self::Future { ... }
/// }
/// ```
///
/// Service can have mutable state that influence computation.
/// This service could be rewritten as a simple function:
/// Sometimes it is not necessary to implement the Service trait. For example, the above service
/// could be rewritten as a simple function and passed to [fn_service](fn.fn_service.html).
///
/// ```rust,ignore
/// async fn my_service(req: u8) -> Result<u64, MyError>;
@@ -82,7 +85,7 @@ pub trait Service {
/// Returns `Ready` when the service is able to process requests.
///
/// If the service is at capacity, then `NotReady` is returned and the task
/// If the service is at capacity, then `Pending` is returned and the task
/// is notified when the service becomes ready again. This function is
/// expected to be called while on a task.
///
@@ -90,11 +93,9 @@ pub trait Service {
/// It is permitted for the service to return `Ready` from a `poll_ready`
/// call and the next invocation of `call` results in an error.
///
/// There are several notes to consider:
///
/// # Notes
/// 1. `.poll_ready()` might be called on different task from actual service call.
///
/// 2. In case of chained services, `.poll_ready()` get called for all services at once.
/// 1. In case of chained services, `.poll_ready()` get called for all services at once.
fn poll_ready(&mut self, ctx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
/// Process the request and return the response asynchronously.
@@ -128,7 +129,7 @@ pub trait Service {
/// Map this service's error to a different error, returning a new service.
///
/// This function is similar to the `Result::map_err` where it will change
/// the error type of the underlying service. This is useful for example to
/// the error type of the underlying service. For example, this can be useful to
/// ensure that services have the same error type.
///
/// Note that this function consumes the receiving service and returns a
@@ -142,42 +143,42 @@ pub trait Service {
}
}
/// Creates new `Service` values.
/// Factory for creating `Service`s.
///
/// Acts as a service factory. This is useful for cases where new `Service`
/// values must be produced. One case is a TCP server listener. The listener
/// accepts new TCP streams, obtains a new `Service` value using the
/// `ServiceFactory` trait, and uses that new `Service` value to process inbound
/// Acts as a service factory. This is useful for cases where new `Service`s
/// must be produced. One case is a TCP server listener. The listener
/// accepts new TCP streams, obtains a new `Service` using the
/// `ServiceFactory` trait, and uses the new `Service` to process inbound
/// requests on that new TCP stream.
///
/// `Config` is a service factory configuration type.
pub trait ServiceFactory {
/// Requests handled by the service.
/// Requests handled by the created services.
type Request;
/// Responses given by the service
/// Responses given by the created services.
type Response;
/// Errors produced by the service
/// Errors produced by the created services.
type Error;
/// Service factory configuration
/// Service factory configuration.
type Config;
/// The `Service` value created by this factory
/// The kind of `Service` created by this factory.
type Service: Service<
Request = Self::Request,
Response = Self::Response,
Error = Self::Error,
>;
/// Errors produced while building a service.
/// Errors potentially raised while building a service.
type InitError;
/// The future of the `Service` instance.
type Future: Future<Output = Result<Self::Service, Self::InitError>>;
/// Create and return a new service value asynchronously.
/// Create and return a new service asynchronously.
fn new_service(&self, cfg: Self::Config) -> Self::Future;
/// Map this service's output to a different type, returning a new service
@@ -361,10 +362,7 @@ where
}
pub mod dev {
pub use crate::and_then::{AndThenService, AndThenServiceFactory};
pub use crate::and_then_apply_fn::{AndThenApplyFn, AndThenApplyFnFactory};
pub use crate::apply::{Apply, ApplyServiceFactory};
pub use crate::apply_cfg::{ApplyConfigService, ApplyConfigServiceFactory};
pub use crate::fn_service::{
FnService, FnServiceConfig, FnServiceFactory, FnServiceNoConfig,
};
@@ -372,7 +370,6 @@ pub mod dev {
pub use crate::map_config::{MapConfig, UnitConfig};
pub use crate::map_err::{MapErr, MapErrServiceFactory};
pub use crate::map_init_err::MapInitErr;
pub use crate::then::{ThenService, ThenServiceFactory};
pub use crate::transform::ApplyTransform;
pub use crate::transform_err::TransformMapInitErr;
}

View File

@@ -46,7 +46,12 @@ impl<T: Service> Pipeline<T> {
///
/// Note that this function consumes the receiving service and returns a
/// wrapped version of it.
pub fn and_then<F, U>(self, service: F) -> Pipeline<AndThenService<T, U>>
pub fn and_then<F, U>(
self,
service: F,
) -> Pipeline<
impl Service<Request = T::Request, Response = U::Response, Error = T::Error> + Clone,
>
where
Self: Sized,
F: IntoService<U>,
@@ -65,7 +70,7 @@ impl<T: Service> Pipeline<T> {
self,
service: I,
f: F,
) -> Pipeline<AndThenApplyFn<T, U, F, Fut, Res, Err>>
) -> Pipeline<impl Service<Request = T::Request, Response = Res, Error = Err> + Clone>
where
Self: Sized,
I: IntoService<U>,
@@ -84,7 +89,12 @@ impl<T: Service> Pipeline<T> {
///
/// Note that this function consumes the receiving pipeline and returns a
/// wrapped version of it.
pub fn then<F, U>(self, service: F) -> Pipeline<ThenService<T, U>>
pub fn then<F, U>(
self,
service: F,
) -> Pipeline<
impl Service<Request = T::Request, Response = U::Response, Error = T::Error> + Clone,
>
where
Self: Sized,
F: IntoService<U>,
@@ -168,7 +178,23 @@ pub struct PipelineFactory<T> {
impl<T: ServiceFactory> PipelineFactory<T> {
/// Call another service after call to this one has resolved successfully.
pub fn and_then<F, U>(self, factory: F) -> PipelineFactory<AndThenServiceFactory<T, U>>
pub fn and_then<F, U>(
self,
factory: F,
) -> PipelineFactory<
impl ServiceFactory<
Request = T::Request,
Response = U::Response,
Error = T::Error,
Config = T::Config,
InitError = T::InitError,
Service = impl Service<
Request = T::Request,
Response = U::Response,
Error = T::Error,
> + Clone,
> + Clone,
>
where
Self: Sized,
T::Config: Clone,
@@ -193,7 +219,16 @@ impl<T: ServiceFactory> PipelineFactory<T> {
self,
factory: I,
f: F,
) -> PipelineFactory<AndThenApplyFnFactory<T, U, F, Fut, Res, Err>>
) -> PipelineFactory<
impl ServiceFactory<
Request = T::Request,
Response = Res,
Error = Err,
Config = T::Config,
InitError = T::InitError,
Service = impl Service<Request = T::Request, Response = Res, Error = Err> + Clone,
> + Clone,
>
where
Self: Sized,
T::Config: Clone,
@@ -214,7 +249,23 @@ impl<T: ServiceFactory> PipelineFactory<T> {
///
/// Note that this function consumes the receiving pipeline and returns a
/// wrapped version of it.
pub fn then<F, U>(self, factory: F) -> PipelineFactory<ThenServiceFactory<T, U>>
pub fn then<F, U>(
self,
factory: F,
) -> PipelineFactory<
impl ServiceFactory<
Request = T::Request,
Response = U::Response,
Error = T::Error,
Config = T::Config,
InitError = T::InitError,
Service = impl Service<
Request = T::Request,
Response = U::Response,
Error = T::Error,
> + Clone,
> + Clone,
>
where
Self: Sized,
T::Config: Clone,

View File

@@ -1,18 +1,16 @@
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use super::{Service, ServiceFactory};
use crate::cell::Cell;
/// Service for the `then` combinator, chaining a computation onto the end of
/// another service.
///
/// This is created by the `ServiceExt::then` method.
pub struct ThenService<A, B> {
a: A,
b: Cell<B>,
}
/// This is created by the `Pipeline::then` method.
pub(crate) struct ThenService<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B> ThenService<A, B> {
/// Create new `.then()` combinator
@@ -21,19 +19,13 @@ impl<A, B> ThenService<A, B> {
A: Service,
B: Service<Request = Result<A::Response, A::Error>, Error = A::Error>,
{
Self { a, b: Cell::new(b) }
Self(Rc::new(RefCell::new((a, b))))
}
}
impl<A, B> Clone for ThenService<A, B>
where
A: Clone,
{
impl<A, B> Clone for ThenService<A, B> {
fn clone(&self) -> Self {
ThenService {
a: self.a.clone(),
b: self.b.clone(),
}
ThenService(self.0.clone())
}
}
@@ -47,9 +39,10 @@ where
type Error = B::Error;
type Future = ThenServiceResponse<A, B>;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let not_ready = !self.a.poll_ready(ctx)?.is_ready();
if !self.b.get_mut().poll_ready(ctx)?.is_ready() || not_ready {
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let mut srv = self.0.borrow_mut();
let not_ready = !srv.0.poll_ready(cx)?.is_ready();
if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
Poll::Pending
} else {
Poll::Ready(Ok(()))
@@ -58,13 +51,13 @@ where
fn call(&mut self, req: A::Request) -> Self::Future {
ThenServiceResponse {
state: State::A(self.a.call(req), Some(self.b.clone())),
state: State::A(self.0.borrow_mut().0.call(req), Some(self.0.clone())),
}
}
}
#[pin_project::pin_project]
pub struct ThenServiceResponse<A, B>
pub(crate) struct ThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = Result<A::Response, A::Error>>,
@@ -73,13 +66,13 @@ where
state: State<A, B>,
}
#[pin_project::pin_project]
#[pin_project::pin_project(project = StateProj)]
enum State<A, B>
where
A: Service,
B: Service<Request = Result<A::Response, A::Error>>,
{
A(#[pin] A::Future, Option<Cell<B>>),
A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
@@ -91,36 +84,33 @@ where
{
type Output = Result<B::Response, B::Error>;
#[pin_project::project]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
#[project]
match this.state.as_mut().project() {
State::A(fut, b) => match fut.poll(cx) {
StateProj::A(fut, b) => match fut.poll(cx) {
Poll::Ready(res) => {
let mut b = b.take().unwrap();
let b = b.take().unwrap();
this.state.set(State::Empty); // drop fut A
let fut = b.get_mut().call(res);
let fut = b.borrow_mut().1.call(res);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
State::B(fut) => fut.poll(cx).map(|r| {
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
State::Empty => panic!("future must not be polled after it returned `Poll::Ready`"),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/// `.then()` service factory combinator
pub struct ThenServiceFactory<A, B> {
a: A,
b: B,
}
pub(crate) struct ThenServiceFactory<A, B>(Rc<(A, B)>);
impl<A, B> ThenServiceFactory<A, B>
where
@@ -135,7 +125,7 @@ where
{
/// Create new `AndThen` combinator
pub(crate) fn new(a: A, b: B) -> Self {
Self { a, b }
Self(Rc::new((a, b)))
}
}
@@ -160,28 +150,19 @@ where
type Future = ThenServiceFactoryResponse<A, B>;
fn new_service(&self, cfg: A::Config) -> Self::Future {
ThenServiceFactoryResponse::new(
self.a.new_service(cfg.clone()),
self.b.new_service(cfg),
)
let srv = &*self.0;
ThenServiceFactoryResponse::new(srv.0.new_service(cfg.clone()), srv.1.new_service(cfg))
}
}
impl<A, B> Clone for ThenServiceFactory<A, B>
where
A: Clone,
B: Clone,
{
impl<A, B> Clone for ThenServiceFactory<A, B> {
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
b: self.b.clone(),
}
Self(self.0.clone())
}
}
#[pin_project::pin_project]
pub struct ThenServiceFactoryResponse<A, B>
pub(crate) struct ThenServiceFactoryResponse<A, B>
where
A: ServiceFactory,
B: ServiceFactory<
@@ -324,11 +305,11 @@ mod tests {
let res = srv.call(Ok("srv1")).await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv1", "ok")));
assert_eq!(res.unwrap(), ("srv1", "ok"));
let res = srv.call(Err("srv")).await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv2", "err")));
assert_eq!(res.unwrap(), ("srv2", "err"));
}
#[actix_rt::test]
@@ -340,10 +321,10 @@ mod tests {
let mut srv = factory.new_service(&()).await.unwrap();
let res = srv.call(Ok("srv1")).await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv1", "ok")));
assert_eq!(res.unwrap(), ("srv1", "ok"));
let res = srv.call(Err("srv")).await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv2", "err")));
assert_eq!(res.unwrap(), ("srv2", "err"));
}
}

View File

@@ -211,7 +211,7 @@ where
state: ApplyTransformFutureState<T, S>,
}
#[pin_project::pin_project]
#[pin_project::pin_project(project = ApplyTransformFutureStateProj)]
pub enum ApplyTransformFutureState<T, S>
where
S: ServiceFactory,
@@ -228,13 +228,11 @@ where
{
type Output = Result<T::Transform, T::InitError>;
#[pin_project::project]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
#[project]
match this.state.as_mut().project() {
ApplyTransformFutureState::A(fut) => match fut.poll(cx)? {
ApplyTransformFutureStateProj::A(fut) => match fut.poll(cx)? {
Poll::Ready(srv) => {
let fut = this.store.0.new_transform(srv);
this.state.set(ApplyTransformFutureState::B(fut));
@@ -242,7 +240,7 @@ where
}
Poll::Pending => Poll::Pending,
},
ApplyTransformFutureState::B(fut) => fut.poll(cx),
ApplyTransformFutureStateProj::B(fut) => fut.poll(cx),
}
}
}

View File

@@ -1,5 +1,11 @@
# Changes
## [1.0.1] - 2020-05-19
* Replace deprecated `net2` crate with `socket2`
* Remove unused `futures` dependency
## [1.0.0] - 2019-12-11
* Update actix-server to 1.0.0

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-testing"
version = "1.0.0"
version = "1.0.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix testing utils"
keywords = ["network", "framework", "async", "futures"]
@@ -8,9 +8,10 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-testing/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
edition = "2018"
workspace = ".."
readme = "README.md"
[lib]
name = "actix_testing"
@@ -23,5 +24,4 @@ actix-server = "1.0.0"
actix-service = "1.0.0"
log = "0.4"
net2 = "0.2"
futures = "0.3.1"
socket2 = "0.3"

View File

@@ -1,13 +1,13 @@
//! Various helpers for Actix applications to use during testing.
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)]
#![allow(clippy::type_complexity, clippy::needless_doctest_main)]
use std::sync::mpsc;
use std::{net, thread};
use actix_rt::{net::TcpStream, System};
use actix_server::{Server, ServerBuilder, ServiceFactory};
use net2::TcpBuilder;
use socket2::{Domain, Protocol, Socket, Type};
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub use actix_macros::test;
@@ -110,10 +110,11 @@ impl TestServer {
/// Get firat available unused local address
pub fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = TcpBuilder::new_v4().unwrap();
socket.bind(&addr).unwrap();
socket.reuse_address(true).unwrap();
let tcp = socket.to_tcp_listener().unwrap();
let socket =
Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
socket.bind(&addr.into()).unwrap();
socket.set_reuse_address(true).unwrap();
let tcp = socket.into_tcp_listener();
tcp.local_addr().unwrap()
}
}

View File

@@ -1,10 +1,24 @@
# Changes
## [0.3.3] - 2020-07-14
### Changed
* Update parking_lot to 0.11
## [0.3.2] - 2020-05-20
## Added
* Implement `std::error::Error` for `BlockingError` [#120]
[#120]: https://github.com/actix/actix-net/pull/120
## [0.3.1] - 2019-12-12
### Changed
* Use parking_lot 0.10
* Update parking_lot to 0.10
## [0.3.0] - 2019-12-02

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-threadpool"
version = "0.3.1"
version = "0.3.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix thread pool for sync code"
keywords = ["actix", "network", "framework", "async", "futures"]
@@ -8,7 +8,7 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-threadpool/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018"
workspace = ".."
@@ -20,7 +20,7 @@ path = "src/lib.rs"
[dependencies]
derive_more = "0.99.2"
futures-channel = "0.3.1"
parking_lot = "0.10"
parking_lot = "0.11"
lazy_static = "1.3"
log = "0.4"
num_cpus = "1.10"

View File

@@ -48,6 +48,8 @@ pub enum BlockingError<E: fmt::Debug> {
Canceled,
}
impl<E: fmt::Debug> std::error::Error for BlockingError<E> {}
/// Execute blocking function on a thread pool, returns future that resolves
/// to result of the function execution.
pub fn run<F, I, E>(f: F) -> CpuFuture<I, E>

View File

@@ -1,5 +1,25 @@
# Changes
## Unreleased
## 2.0.0-alpha.2 - 2020-08-17
### Changed
* Update `rustls` dependency to 0.18
* Update `tokio-rustls` dependency to 0.14
* Update `webpki-roots` dependency to 0.20
## [2.0.0-alpha.1] - 2020-03-03
### Changed
* Update `rustls` dependency to 0.17
* Update `tokio-rustls` dependency to 0.13
* Update `webpki-roots` dependency to 0.19
## [1.0.0] - 2019-12-11
* 1.0.0 release

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-tls"
version = "1.0.0"
version = "2.0.0-alpha.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix tls services"
keywords = ["network", "framework", "async", "futures"]
@@ -8,7 +8,7 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-tls/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
edition = "2018"
workspace = ".."
@@ -33,12 +33,12 @@ nativetls = ["native-tls", "tokio-tls"]
[dependencies]
actix-service = "1.0.0"
actix-codec = "0.2.0"
actix-utils = "1.0.0"
actix-codec = "0.3.0"
actix-utils = "2.0.0"
actix-rt = "1.0.0"
derive_more = "0.99.2"
either = "1.5.2"
futures = "0.3.1"
futures-util = { version = "0.3.4", default-features = false }
log = "0.4"
# openssl
@@ -46,10 +46,10 @@ open-ssl = { version="0.10", package = "openssl", optional = true }
tokio-openssl = { version = "0.4.0", optional = true }
# rustls
rust-tls = { version = "0.16.0", package = "rustls", optional = true }
rust-tls = { version = "0.18.0", package = "rustls", optional = true }
webpki = { version = "0.21", optional = true }
webpki-roots = { version = "0.17", optional = true }
tokio-rustls = { version = "0.12.0", optional = true }
webpki-roots = { version = "0.20", optional = true }
tokio-rustls = { version = "0.14.0", optional = true }
# native-tls
native-tls = { version="0.2", optional = true }

View File

@@ -4,7 +4,7 @@ use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::{Service, ServiceFactory};
use actix_utils::counter::Counter;
use futures::future::{self, FutureExt, LocalBoxFuture, TryFutureExt};
use futures_util::future::{self, FutureExt, LocalBoxFuture, TryFutureExt};
pub use native_tls::Error;
pub use tokio_tls::{TlsAcceptor, TlsStream};

View File

@@ -9,7 +9,7 @@ pub use tokio_openssl::{HandshakeError, SslStream};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::{Service, ServiceFactory};
use actix_utils::counter::{Counter, CounterGuard};
use futures::future::{ok, FutureExt, LocalBoxFuture, Ready};
use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready};
use crate::MAX_CONN_COUNTER;
@@ -105,7 +105,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin> Future for AcceptorServiceResponse<T> {
type Output = Result<SslStream<T>, HandshakeError<T>>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let io = futures::ready!(Pin::new(&mut self.fut).poll(cx))?;
let io = futures_util::ready!(Pin::new(&mut self.fut).poll(cx))?;
Poll::Ready(Ok(io))
}
}

View File

@@ -8,7 +8,7 @@ use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::{Service, ServiceFactory};
use actix_utils::counter::{Counter, CounterGuard};
use futures::future::{ok, Ready};
use futures_util::future::{ok, Ready};
use tokio_rustls::{Accept, TlsAcceptor};
pub use rust_tls::{ServerConfig, Session};
@@ -108,7 +108,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin> Future for AcceptorServiceFut<T> {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
let res = futures::ready!(Pin::new(&mut this.fut).poll(cx));
let res = futures_util::ready!(Pin::new(&mut this.fut).poll(cx));
match res {
Ok(io) => Poll::Ready(Ok(io)),
Err(e) => Poll::Ready(Err(e)),

5
actix-tracing/CHANGES.md Normal file
View File

@@ -0,0 +1,5 @@
# Changes
## [0.1.0] - 2020-01-15
* Initial release

26
actix-tracing/Cargo.toml Normal file
View File

@@ -0,0 +1,26 @@
[package]
name = "actix-tracing"
version = "0.1.0"
authors = ["Rajasekharan Vengalil <avranju@gmail.com>"]
description = "Support for tokio tracing with Actix services"
keywords = ["network", "framework", "tracing"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-tracing/"
categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0"
edition = "2018"
[lib]
name = "actix_tracing"
path = "src/lib.rs"
[dependencies]
actix-service = "1.0.4"
futures-util = { version = "0.3.4", default-features = false }
tracing = "0.1"
tracing-futures = "0.2"
[dev_dependencies]
actix-rt = "1.0"
slab = "0.4"

261
actix-tracing/src/lib.rs Normal file
View File

@@ -0,0 +1,261 @@
//! Actix tracing - support for tokio tracing with Actix services.
#![deny(rust_2018_idioms, warnings)]
use std::marker::PhantomData;
use std::task::{Context, Poll};
use actix_service::{
apply, dev::ApplyTransform, IntoServiceFactory, Service, ServiceFactory, Transform,
};
use futures_util::future::{ok, Either, Ready};
use tracing_futures::{Instrument, Instrumented};
/// A `Service` implementation that automatically enters/exits tracing spans
/// for the wrapped inner service.
#[derive(Clone)]
pub struct TracingService<S, F> {
inner: S,
make_span: F,
}
impl<S, F> TracingService<S, F> {
pub fn new(inner: S, make_span: F) -> Self {
TracingService { inner, make_span }
}
}
impl<S, F> Service for TracingService<S, F>
where
S: Service,
F: Fn(&S::Request) -> Option<tracing::Span>,
{
type Request = S::Request;
type Response = S::Response;
type Error = S::Error;
type Future = Either<S::Future, Instrumented<S::Future>>;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(ctx)
}
fn call(&mut self, req: Self::Request) -> Self::Future {
let span = (self.make_span)(&req);
let _enter = span.as_ref().map(|s| s.enter());
let fut = self.inner.call(req);
// make a child span to track the future's execution
if let Some(span) = span
.clone()
.map(|span| tracing::span!(parent: &span, tracing::Level::INFO, "future"))
{
Either::Right(fut.instrument(span))
} else {
Either::Left(fut)
}
}
}
/// A `Transform` implementation that wraps services with a [`TracingService`].
///
/// [`TracingService`]: struct.TracingService.html
pub struct TracingTransform<S, U, F> {
make_span: F,
_p: PhantomData<fn(S, U)>,
}
impl<S, U, F> TracingTransform<S, U, F> {
pub fn new(make_span: F) -> Self {
TracingTransform {
make_span,
_p: PhantomData,
}
}
}
impl<S, U, F> Transform<S> for TracingTransform<S, U, F>
where
S: Service,
U: ServiceFactory<
Request = S::Request,
Response = S::Response,
Error = S::Error,
Service = S,
>,
F: Fn(&S::Request) -> Option<tracing::Span> + Clone,
{
type Request = S::Request;
type Response = S::Response;
type Error = S::Error;
type Transform = TracingService<S, F>;
type InitError = U::InitError;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(TracingService::new(service, self.make_span.clone()))
}
}
/// Wraps the provided service factory with a transform that automatically
/// enters/exits the given span.
///
/// The span to be entered/exited can be provided via a closure. The closure
/// is passed in a reference to the request being handled by the service.
///
/// For example:
/// ```rust,ignore
/// let traced_service = trace(
/// web_service,
/// |req: &Request| Some(span!(Level::INFO, "request", req.id))
/// );
/// ```
pub fn trace<S, U, F>(
service_factory: U,
make_span: F,
) -> ApplyTransform<TracingTransform<S::Service, S, F>, S>
where
S: ServiceFactory,
F: Fn(&S::Request) -> Option<tracing::Span> + Clone,
U: IntoServiceFactory<S>,
{
apply(
TracingTransform::new(make_span),
service_factory.into_factory(),
)
}
#[cfg(test)]
mod test {
use super::*;
use std::cell::RefCell;
use std::collections::{BTreeMap, BTreeSet};
use std::sync::{Arc, RwLock};
use actix_service::{fn_factory, fn_service};
use slab::Slab;
use tracing::{span, Event, Level, Metadata, Subscriber};
thread_local! {
static SPAN: RefCell<Vec<span::Id>> = RefCell::new(Vec::new());
}
#[derive(Default)]
struct Stats {
entered_spans: BTreeSet<u64>,
exited_spans: BTreeSet<u64>,
events_count: BTreeMap<u64, usize>,
}
#[derive(Default)]
struct Inner {
spans: Slab<&'static Metadata<'static>>,
stats: Stats,
}
#[derive(Clone, Default)]
struct TestSubscriber {
inner: Arc<RwLock<Inner>>,
}
impl Subscriber for TestSubscriber {
fn enabled(&self, _metadata: &Metadata<'_>) -> bool {
true
}
fn new_span(&self, span: &span::Attributes<'_>) -> span::Id {
let id = self.inner.write().unwrap().spans.insert(span.metadata());
span::Id::from_u64(id as u64 + 1)
}
fn record(&self, _span: &span::Id, _values: &span::Record<'_>) {}
fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) {}
fn event(&self, event: &Event<'_>) {
let id = event
.parent()
.cloned()
.or_else(|| SPAN.with(|current_span| current_span.borrow().last().cloned()))
.unwrap();
*self
.inner
.write()
.unwrap()
.stats
.events_count
.entry(id.into_u64())
.or_insert(0) += 1;
}
fn enter(&self, span: &span::Id) {
self.inner
.write()
.unwrap()
.stats
.entered_spans
.insert(span.into_u64());
SPAN.with(|current_span| {
current_span.borrow_mut().push(span.clone());
});
}
fn exit(&self, span: &span::Id) {
self.inner
.write()
.unwrap()
.stats
.exited_spans
.insert(span.into_u64());
// we are guaranteed that on any given thread, spans are exited in reverse order
SPAN.with(|current_span| {
let leaving = current_span
.borrow_mut()
.pop()
.expect("told to exit span when not in span");
assert_eq!(
&leaving, span,
"told to exit span that was not most recently entered"
);
});
}
}
#[actix_rt::test]
async fn service_call() {
let service_factory = fn_factory(|| {
ok::<_, ()>(fn_service(|req: &'static str| {
tracing::event!(Level::TRACE, "It's happening - {}!", req);
ok::<_, ()>(())
}))
});
let subscriber = TestSubscriber::default();
let _guard = tracing::subscriber::set_default(subscriber.clone());
let span_svc = span!(Level::TRACE, "span_svc");
let trace_service_factory = trace(service_factory, |_: &&str| Some(span_svc.clone()));
let mut service = trace_service_factory.new_service(()).await.unwrap();
service.call("boo").await.unwrap();
let id = span_svc.id().unwrap().into_u64();
assert!(subscriber
.inner
.read()
.unwrap()
.stats
.entered_spans
.contains(&id));
assert!(subscriber
.inner
.read()
.unwrap()
.stats
.exited_spans
.contains(&id));
assert_eq!(subscriber.inner.read().unwrap().stats.events_count[&id], 1);
}
}

View File

@@ -1,5 +1,15 @@
# Changes
## Unreleased - 2020-xx-xx
## 2.0.0 - 2020-08-23
* No changes from beta 1.
## 2.0.0-beta.1 - 2020-08-19
* Upgrade `tokio-util` to `0.3`.
* Remove unsound custom Cell and use `std::cell::RefCell` instead, as well as `actix-service`.
* Rename method to correctly spelled `LocalWaker::is_registered`.
## [1.0.6] - 2020-01-08
* Add `Clone` impl for `condition::Waiter`

View File

@@ -1,14 +1,14 @@
[package]
name = "actix-utils"
version = "1.0.6"
version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix utils - various actix net related services"
description = "Various network related services and utilities for the Actix ecosystem."
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-utils/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
edition = "2018"
[lib]
@@ -16,13 +16,15 @@ name = "actix_utils"
path = "src/lib.rs"
[dependencies]
actix-service = "1.0.1"
actix-rt = "1.0.0"
actix-codec = "0.2.0"
bitflags = "1.2"
actix-codec = "0.3.0"
actix-rt = "1.1.1"
actix-service = "1.0.6"
bitflags = "1.2.1"
bytes = "0.5.3"
either = "1.5.3"
futures = "0.3.1"
pin-project = "0.4.6"
futures-channel = { version = "0.3.4", default-features = false }
futures-sink = { version = "0.3.4", default-features = false }
futures-util = { version = "0.3.4", default-features = false }
log = "0.4"
pin-project = "0.4.17"
slab = "0.4"

View File

@@ -1,48 +0,0 @@
//! Custom cell impl
use std::cell::UnsafeCell;
use std::fmt;
use std::rc::Rc;
pub(crate) struct Cell<T> {
pub(crate) inner: Rc<UnsafeCell<T>>,
}
impl<T> Clone for Cell<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T: fmt::Debug> fmt::Debug for Cell<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
impl<T> Cell<T> {
pub(crate) fn new(inner: T) -> Self {
Self {
inner: Rc::new(UnsafeCell::new(inner)),
}
}
pub(crate) fn strong_count(&self) -> usize {
Rc::strong_count(&self.inner)
}
pub(crate) fn get_ref(&self) -> &T {
unsafe { &*self.inner.as_ref().get() }
}
pub(crate) fn get_mut(&mut self) -> &mut T {
unsafe { &mut *self.inner.as_ref().get() }
}
#[allow(clippy::mut_from_ref)]
pub(crate) unsafe fn get_mut_unsafe(&self) -> &mut T {
&mut *self.inner.as_ref().get()
}
}

View File

@@ -1,14 +1,15 @@
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use slab::Slab;
use crate::cell::Cell;
use crate::task::LocalWaker;
/// Condition allows to notify multiple receivers at the same time
pub struct Condition(Cell<Inner>);
pub struct Condition(Rc<RefCell<Inner>>);
struct Inner {
data: Slab<Option<LocalWaker>>,
@@ -22,12 +23,12 @@ impl Default for Condition {
impl Condition {
pub fn new() -> Condition {
Condition(Cell::new(Inner { data: Slab::new() }))
Condition(Rc::new(RefCell::new(Inner { data: Slab::new() })))
}
/// Get condition waiter
pub fn wait(&mut self) -> Waiter {
let token = self.0.get_mut().data.insert(None);
let token = self.0.borrow_mut().data.insert(None);
Waiter {
token,
inner: self.0.clone(),
@@ -36,7 +37,7 @@ impl Condition {
/// Notify all waiters
pub fn notify(&self) {
let inner = self.0.get_ref();
let inner = self.0.borrow();
for item in inner.data.iter() {
if let Some(waker) = item.1 {
waker.wake();
@@ -54,12 +55,12 @@ impl Drop for Condition {
#[must_use = "Waiter do nothing unless polled"]
pub struct Waiter {
token: usize,
inner: Cell<Inner>,
inner: Rc<RefCell<Inner>>,
}
impl Clone for Waiter {
fn clone(&self) -> Self {
let token = unsafe { self.inner.get_mut_unsafe() }.data.insert(None);
let token = self.inner.borrow_mut().data.insert(None);
Waiter {
token,
inner: self.inner.clone(),
@@ -73,7 +74,8 @@ impl Future for Waiter {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
let inner = unsafe { this.inner.get_mut().data.get_unchecked_mut(this.token) };
let mut inner = this.inner.borrow_mut();
let inner = unsafe { inner.data.get_unchecked_mut(this.token) };
if inner.is_none() {
let waker = LocalWaker::default();
waker.register(cx.waker());
@@ -89,14 +91,14 @@ impl Future for Waiter {
impl Drop for Waiter {
fn drop(&mut self) {
self.inner.get_mut().data.remove(self.token);
self.inner.borrow_mut().data.remove(self.token);
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::future::lazy;
use futures_util::future::lazy;
#[actix_rt::test]
async fn test_condition() {
@@ -107,7 +109,7 @@ mod tests {
Poll::Pending
);
cond.notify();
assert_eq!(waiter.await, ());
waiter.await;
let mut waiter = cond.wait();
assert_eq!(
@@ -121,7 +123,7 @@ mod tests {
);
drop(cond);
assert_eq!(waiter.await, ());
assert_eq!(waiter2.await, ());
waiter.await;
waiter2.await;
}
}

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