Move seastar to a submodule
Instead of urchin being layered on top of seastar, move seastar to a subdirectory (as a submodule) and use seastar.pc/libseastar.a to link with it.
This commit is contained in:
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,6 +1,6 @@
|
||||
[submodule "dpdk"]
|
||||
path = dpdk
|
||||
url = ../dpdk
|
||||
[submodule "seastar"]
|
||||
path = seastar
|
||||
url = ../seastar
|
||||
[submodule "swagger-ui"]
|
||||
path = swagger-ui
|
||||
url = ../urchin-swagger-ui
|
||||
|
||||
177
LICENSE.seastar
177
LICENSE.seastar
@@ -1,177 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
11
NOTICE
11
NOTICE
@@ -1,11 +0,0 @@
|
||||
Seastar Framework
|
||||
Copyright 2015 Cloudius Systems
|
||||
|
||||
This works contains software from the OSv project (http://osv.io), licensed
|
||||
under the BSD license.
|
||||
|
||||
This work contains software from the DPDK project (http://dpdk.org), licensed
|
||||
under the BSD license. The software is under the dpdk/ directory.
|
||||
|
||||
This works contains software from https://github.com/hideo55/cpp-HyperLogLog, licensed
|
||||
under the MIT license.
|
||||
108
README-OSv.md
108
README-OSv.md
@@ -1,108 +0,0 @@
|
||||
Running Seastar on OSv
|
||||
======================
|
||||
|
||||
1. Compiling Seastar for OSv
|
||||
----------------------------
|
||||
|
||||
Before compiling Seastar, configure it with the following command:
|
||||
|
||||
./configure.py --so --disable-hwloc \
|
||||
--cflags="-DDEFAULT_ALLOCATOR -fvisibility=default -DHAVE_OSV -I../osv/include" \
|
||||
--mode release
|
||||
|
||||
Or more easily, use the "--with-osv=..." shortcut for all the above settings:
|
||||
|
||||
./configure.py --mode release --with-osv=../osv
|
||||
|
||||
|
||||
Explanation of these configuration options:
|
||||
* The "--so" option is needed so that the Seastar applications, such as
|
||||
httpd, are built as shared objects instead of ordinary executables.
|
||||
Note the "--pie" option also exists, but because of bug #352 in OSv,
|
||||
and the fact that Seastar uses thread_local in one place, these PIEs
|
||||
cannot be run on OSv.
|
||||
* The "--disable-hwloc" option is needed so that Seastar does not attempt
|
||||
to use the complex NUMA-discovery library, which isn't supported on OSv
|
||||
(and isn't relevant anyway, because VMs with NUMA are not (yet) common.
|
||||
* The "-DEFAULT_ALLOCATOR" uses in Seastar the system's regular malloc()
|
||||
and free(), instead of redefining them. Without this flag, what seems
|
||||
to happen is that some code compiled into the OSv kernel (notably,
|
||||
libboost_program_options.a) uses the standard malloc(), while inline
|
||||
code compiled into Seastar uses the Seastar free() to try and free that
|
||||
memory, resulting in a spectacular crash.
|
||||
* The "-fvisibility=default" option disables the "-fvisibility=hidden"
|
||||
option which is hard-coded into Seastar's build file. Supposedly
|
||||
"-fvisibility=hidden" provides some better optimization in some cases,
|
||||
but it also means OSv can't find main() in the generated shared object!
|
||||
So either we use "-fvisibility=default", as suggested here, or
|
||||
alternatively, make *only* main() visible - for example, to make httpd's
|
||||
main() visible add in apps/httpd/httpd.cc, before the main() definition,
|
||||
the chant: [[gnu::visibility("default")]]
|
||||
* The "-DHAVE_OSV" conditionally compiles code in Seastar that relies
|
||||
on OSv APIs (currently, this enables virtio device assignment).
|
||||
This OSv-specific code relies on some OSv header files, which is
|
||||
why the "-I../osv/include" is needed (where "../osv" is where the
|
||||
OSv source tree is open).
|
||||
* The "--mode release" is to compile only the release build. You'll
|
||||
usually not want to debug Seastar on OSv (it's easier to debug on Linux).
|
||||
|
||||
|
||||
2. Building a Seastar-httpd module for OSv
|
||||
------------------------------------------
|
||||
|
||||
As an example, we'll build a "seastar" module in OSv running Seastar's
|
||||
httpd application.
|
||||
|
||||
In the OSv working directory, create a directory apps/seastar in it put:
|
||||
|
||||
* a link to the httpd binary in the Seastar working directory. I.e.,
|
||||
ln -s ../../../seastar/build/release/apps/httpd/httpd httpd
|
||||
|
||||
* A usr.manifest file, adding only this single "httpd" executable to the image:
|
||||
/httpd: ${MODULE_DIR}/httpd
|
||||
|
||||
* A module.py file with a default command line:
|
||||
|
||||
from osv.modules import api
|
||||
default = api.run(cmdline="/httpd --no-handle-interrupt")
|
||||
|
||||
The "--no-handle-interrupt" is needed so that Seastar does not attempt to
|
||||
use signalfd() to capture ^C. signalfd is not yet available on OSv, and
|
||||
the ^C handling is not a very important feature of Seastar anyway.
|
||||
|
||||
Also note that currently we cannot specify "--network-stack=native", because
|
||||
neither vhost nor a more efficient mechanism for "virtio assignment" is yet
|
||||
complete on OSv. So we must keep the default (which is "--network-stack=posix")
|
||||
which uses the OS's Posix networking APIs, which OSv fully supports.
|
||||
|
||||
|
||||
3. Running the seastar module on OSv
|
||||
-------------------------------------
|
||||
|
||||
To run the Seastar httpd application, using the module defined above, do,
|
||||
as usual, in the OSv working directory:
|
||||
|
||||
$ make image=seastar -j4
|
||||
$ sudo scripts/run.py -nvV
|
||||
|
||||
This will open an HTTP server on port 10000 of the VM. For example, if the
|
||||
above creates a VM with an IP address of 192.168.122.89, we can test it as
|
||||
following:
|
||||
|
||||
$ curl 192.168.122.89:10000
|
||||
<html><head><title>this is the future</title></head><body><p>Future!!</p></body></html>
|
||||
|
||||
4. Debugging OSv with the Seastar application
|
||||
---------------------------------------------
|
||||
|
||||
If you want to debug OSv (not the Seastar application) in relation to the
|
||||
way it runs Seastar, you'll want the "httpd" shared object to be available
|
||||
to gdb.
|
||||
Unfortunately, the object lookup code in "osv syms" (translate() in loader.py)
|
||||
does not seem to look for objects in apps/, so until we fix this, we need
|
||||
to put a link to httpd in a well-known place, such as build/release. So
|
||||
do this in the OSv top directory:
|
||||
ln -s ../../apps/seastar/httpd build/release/httpd
|
||||
|
||||
Note you'll need to repeat this if you do "make clean" (as "make clean"
|
||||
removes everything in build/release).
|
||||
348
README.md
348
README.md
@@ -1,348 +0,0 @@
|
||||
Seastar
|
||||
=======
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
SeaStar is an event-driven framework allowing you to write non-blocking,
|
||||
asynchronous code in a relatively straightforward manner (once understood).
|
||||
It is based on [futures](http://en.wikipedia.org/wiki/Futures_and_promises).
|
||||
|
||||
Building Seastar
|
||||
--------------------
|
||||
|
||||
|
||||
### Building seastar on Fedora 21
|
||||
|
||||
Installing required packages:
|
||||
```
|
||||
yum install gcc-c++ libaio-devel ninja-build ragel hwloc-devel numactl-devel libpciaccess-devel cryptopp-devel xen-devel boost-devel
|
||||
```
|
||||
|
||||
You then need to run the following to create the "build.ninja" file:
|
||||
```
|
||||
./configure.py
|
||||
```
|
||||
Note it is enough to run this once, and you don't need to repeat it before
|
||||
every build. build.ninja includes a rule which will automatically re-run
|
||||
./configure.py if it changes.
|
||||
|
||||
Then finally:
|
||||
```
|
||||
ninja-build
|
||||
```
|
||||
|
||||
### Building seastar on Fedora 20
|
||||
|
||||
Installing GCC 4.9 for gnu++1y:
|
||||
* Beware that this installation will replace your current GCC version.
|
||||
```
|
||||
yum install fedora-release-rawhide
|
||||
yum --enablerepo rawhide update gcc-c++
|
||||
yum --enablerepo rawhide install libubsan libasan
|
||||
```
|
||||
|
||||
Installing required packages:
|
||||
```
|
||||
yum install libaio-devel ninja-build ragel hwloc-devel numactl-devel libpciaccess-devel cryptopp-devel
|
||||
```
|
||||
|
||||
You then need to run the following to create the "build.ninja" file:
|
||||
```
|
||||
./configure.py
|
||||
```
|
||||
Note it is enough to run this once, and you don't need to repeat it before
|
||||
every build. build.ninja includes a rule which will automatically re-run
|
||||
./configure.py if it changes.
|
||||
|
||||
Then finally:
|
||||
```
|
||||
ninja-build
|
||||
```
|
||||
|
||||
### Building seastar on Ubuntu 14.04
|
||||
|
||||
Installing required packages:
|
||||
```
|
||||
sudo apt-get install libaio-dev ninja-build ragel libhwloc-dev libnuma-dev libpciaccess-dev libcrypto++-dev libboost-all-dev
|
||||
```
|
||||
|
||||
Installing GCC 4.9 for gnu++1y. Unlike the Fedora case above, this will
|
||||
not harm the existing installation of GCC 4.8, and will install an
|
||||
additional set of compilers, and additional commands named gcc-4.9,
|
||||
g++-4.9, etc., that need to be used explicitly, while the "gcc", "g++",
|
||||
etc., commands continue to point to the 4.8 versions.
|
||||
|
||||
```
|
||||
# Install add-apt-repository
|
||||
sudo apt-get install software-properties-common python-software-properties
|
||||
# Use it to add Ubuntu's testing compiler repository
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
|
||||
sudo apt-get update
|
||||
# Install gcc 4.9 and relatives
|
||||
sudo apt-get install g++-4.9
|
||||
# Also set up necessary header file links and stuff (?)
|
||||
sudo apt-get install gcc-4.9-multilib g++-4.9-multilib
|
||||
```
|
||||
|
||||
To compile Seastar explicitly using gcc 4.9, use:
|
||||
```
|
||||
./configure.py --compiler=g++-4.9
|
||||
```
|
||||
|
||||
To compile OSv explicitly using gcc 4.9, use:
|
||||
```
|
||||
make CC=gcc-4.9 CXX=g++-4.9 -j 24
|
||||
```
|
||||
|
||||
### Building seastar in Docker container
|
||||
|
||||
To build a Docker image:
|
||||
|
||||
```
|
||||
docker build -t seastar-dev docker/dev
|
||||
```
|
||||
|
||||
Create an shell function for building insider the container (bash syntax given):
|
||||
|
||||
```
|
||||
$ seabuild() { docker run -v $HOME/seastar/:/seastar -u $(id -u):$(id -g) -w /seastar -t seastar-dev "$@"; }
|
||||
```
|
||||
|
||||
(it is recommended to put this inside your .bashrc or similar)
|
||||
|
||||
To build inside a container:
|
||||
|
||||
```
|
||||
$ seabuild ./configure.py
|
||||
$ seabuild ninja-build
|
||||
```
|
||||
|
||||
### Building with a DPDK network backend
|
||||
|
||||
1. Setup host to compile DPDK:
|
||||
- Ubuntu
|
||||
`sudo apt-get install -y build-essential linux-image-extra-$(uname -r$)`
|
||||
2. Run a configure.py: `./configure.py --enable-dpdk`.
|
||||
3. Run `ninja-build`.
|
||||
|
||||
To run with the DPDK backend for a native stack give the seastar application `--dpdk-pmd 1` parameter.
|
||||
|
||||
You can also configure DPDK as an [external package](README-DPDK.md).
|
||||
|
||||
Futures and promises
|
||||
--------------------
|
||||
|
||||
A *future* is a result of a computation that may not be available yet.
|
||||
Examples include:
|
||||
|
||||
* a data buffer that we are reading from the network
|
||||
* the expiration of a timer
|
||||
* the completion of a disk write
|
||||
* the result computation that requires the values from
|
||||
one or more other futures.
|
||||
|
||||
a *promise* is an object or function that provides you with a future,
|
||||
with the expectation that it will fulfill the future.
|
||||
|
||||
Promises and futures simplify asynchronous programming since they decouple
|
||||
the event producer (the promise) and the event consumer (whoever uses the
|
||||
future). Whether the promise is fulfilled before the future is consumed,
|
||||
or vice versa, does not change the outcome of the code.
|
||||
|
||||
Consuming a future
|
||||
------------------
|
||||
|
||||
You consume a future by using its *then()* method, providing it with a
|
||||
callback (typically a lambda). For example, consider the following
|
||||
operation:
|
||||
|
||||
```C++
|
||||
future<int> get(); // promises an int will be produced eventually
|
||||
future<> put(int) // promises to store an int
|
||||
|
||||
void f() {
|
||||
get().then([] (int value) {
|
||||
put(value + 1).then([] {
|
||||
std::cout << "value stored successfully\n";
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Here, we initiate a *get()* operation, requesting that when it completes, a
|
||||
*put()* operation will be scheduled with an incremented value. We also
|
||||
request that when the *put()* completes, some text will be printed out.
|
||||
|
||||
Chaining futures
|
||||
----------------
|
||||
|
||||
If a *then()* lambda returns a future (call it x), then that *then()*
|
||||
will return a future (call it y) that will receive the same value. This
|
||||
removes the need for nesting lambda blocks; for example the code above
|
||||
could be rewritten as:
|
||||
|
||||
```C++
|
||||
future<int> get(); // promises an int will be produced eventually
|
||||
future<> put(int) // promises to store an int
|
||||
|
||||
void f() {
|
||||
get().then([] (int value) {
|
||||
return put(value + 1);
|
||||
}).then([] {
|
||||
std::cout << "value stored successfully\n";
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Loops
|
||||
-----
|
||||
|
||||
Loops are achieved with a tail call; for example:
|
||||
|
||||
```C++
|
||||
future<int> get(); // promises an int will be produced eventually
|
||||
future<> put(int) // promises to store an int
|
||||
|
||||
future<> loop_to(int end) {
|
||||
if (value == end) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
get().then([end] (int value) {
|
||||
return put(value + 1);
|
||||
}).then([end] {
|
||||
return loop_to(end);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The *make_ready_future()* function returns a future that is already
|
||||
available --- corresponding to the loop termination condition, where
|
||||
no further I/O needs to take place.
|
||||
|
||||
Under the hood
|
||||
--------------
|
||||
|
||||
When the loop above runs, both *then* method calls execute immediately
|
||||
--- but without executing the bodies. What happens is the following:
|
||||
|
||||
1. `get()` is called, initiates the I/O operation, and allocates a
|
||||
temporary structure (call it `f1`).
|
||||
2. The first `then()` call chains its body to `f1` and allocates
|
||||
another temporary structure, `f2`.
|
||||
3. The second `then()` call chains its body to `f2`.
|
||||
|
||||
Again, all this runs immediately without waiting for anything.
|
||||
|
||||
After the I/O operation initiated by `get()` completes, it calls the
|
||||
continuation stored in `f1`, calls it, and frees `f1`. The continuation
|
||||
calls `put()`, which initiates the I/O operation required to perform
|
||||
the store, and allocates a temporary object `f12`, and chains some glue
|
||||
code to it.
|
||||
|
||||
After the I/O operation initiated by `put()` completes, it calls the
|
||||
continuation associated with `f12`, which simply tells it to call the
|
||||
continuation associated with `f2`. This continuation simply calls
|
||||
`loop_to()`. Both `f12` and `f2` are freed. `loop_to()` then calls
|
||||
`get()`, which starts the process all over again, allocating new versions
|
||||
of `f1` and `f2`.
|
||||
|
||||
Handling exceptions
|
||||
-------------------
|
||||
|
||||
If a `.then()` clause throws an exception, the scheduler will catch it
|
||||
and cancel any dependent `.then()` clauses. If you want to trap the
|
||||
exception, add a `.then_wrapped()` clause at the end:
|
||||
|
||||
```C++
|
||||
future<buffer> receive();
|
||||
request parse(buffer buf);
|
||||
future<response> process(request req);
|
||||
future<> send(response resp);
|
||||
|
||||
void f() {
|
||||
receive().then([] (buffer buf) {
|
||||
return process(parse(std::move(buf));
|
||||
}).then([] (response resp) {
|
||||
return send(std::move(resp));
|
||||
}).then([] {
|
||||
f();
|
||||
}).then_wrapped([] (auto&& f) {
|
||||
try {
|
||||
f.get();
|
||||
} catch (std::exception& e) {
|
||||
// your handler goes here
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The previous future is passed as a parameter to the lambda, and its value can
|
||||
be inspected with `f.get()`. When the `get()` variable is called as a
|
||||
function, it will re-throw the exception that aborted processing, and you can
|
||||
then apply any needed error handling. It is essentially a transformation of
|
||||
|
||||
```C++
|
||||
buffer receive();
|
||||
request parse(buffer buf);
|
||||
response process(request req);
|
||||
void send(response resp);
|
||||
|
||||
void f() {
|
||||
try {
|
||||
while (true) {
|
||||
auto req = parse(receive());
|
||||
auto resp = process(std::move(req));
|
||||
send(std::move(resp));
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
// your handler goes here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note, however, that the `.then_wrapped()` clause will be scheduled both when
|
||||
exception occurs or not. Therefore, the mere fact that `.then_wrapped()` is
|
||||
executed does not mean that an exception was thrown. Only the execution of the
|
||||
catch block can guarantee that.
|
||||
|
||||
|
||||
This is shown below:
|
||||
|
||||
```C++
|
||||
|
||||
future<my_type> my_future();
|
||||
|
||||
void f() {
|
||||
receive().then_wrapped([] (future<my_type> f) {
|
||||
try {
|
||||
my_type x = f.get();
|
||||
return do_something(x);
|
||||
} catch (std::exception& e) {
|
||||
// your handler goes here
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
### Setup notes
|
||||
|
||||
SeaStar is a high performance framework and tuned to get the best
|
||||
performance by default. As such, we're tuned towards polling vs interrupt
|
||||
driven. Our assumption is that applications written for SeaStar will be
|
||||
busy handling 100,000 IOPS and beyond. Polling means that each of our
|
||||
cores will consume 100% cpu even when no work is given to it.
|
||||
|
||||
|
||||
Recommended hardware configuration for SeaStar
|
||||
----------------------------------------------
|
||||
|
||||
* CPUs - As much as you need. SeaStar is highly friendly for multi-core and NUMA
|
||||
* NICs - As fast as possible, we recommend 10G or 40G cards. It's possible to use
|
||||
1G to but you may be limited by their capacity.
|
||||
In addition, the more hardware queue per cpu the better for SeaStar.
|
||||
Otherwise we have to emulate that in software.
|
||||
* Disks - Fast SSDs with high number of IOPS.
|
||||
* Client machines - Usually a single client machine can't load our servers.
|
||||
Both memaslap (memcached) and WRK (httpd) cannot over load their matching
|
||||
server counter parts. We recommend running the client on different machine
|
||||
than the servers and use several of them.
|
||||
@@ -1,73 +0,0 @@
|
||||
{
|
||||
"apiVersion": "0.0.1",
|
||||
"swaggerVersion": "1.2",
|
||||
"basePath": "{{Protocol}}://{{Host}}",
|
||||
"resourcePath": "/hello",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"apis": [
|
||||
{
|
||||
"path": "/hello/world/{var1}/{var2}",
|
||||
"operations": [
|
||||
{
|
||||
"method": "GET",
|
||||
"summary": "Returns the number of seconds since the system was booted",
|
||||
"type": "long",
|
||||
"nickname": "hello_world",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name":"var2",
|
||||
"description":"Full path of file or directory",
|
||||
"required":true,
|
||||
"allowMultiple":true,
|
||||
"type":"string",
|
||||
"paramType":"path"
|
||||
},
|
||||
{
|
||||
"name":"var1",
|
||||
"description":"Full path of file or directory",
|
||||
"required":true,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"path"
|
||||
},
|
||||
{
|
||||
"name":"query_enum",
|
||||
"description":"The operation to perform",
|
||||
"required":true,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query",
|
||||
"enum":["VAL1", "VAL2", "VAL3"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"models" : {
|
||||
"my_object": {
|
||||
"id": "my_object",
|
||||
"description": "Demonstrate an object",
|
||||
"properties": {
|
||||
"var1": {
|
||||
"type": "string",
|
||||
"description": "The first parameter in the path"
|
||||
},
|
||||
"var2": {
|
||||
"type": "string",
|
||||
"description": "The second parameter in the path"
|
||||
},
|
||||
"enum_var" : {
|
||||
"type": "string",
|
||||
"description": "Demonstrate an enum returned, note this is not the same enum type of the request",
|
||||
"enum":["VAL1", "VAL2", "VAL3"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#include "http/httpd.hh"
|
||||
#include "http/handlers.hh"
|
||||
#include "http/function_handlers.hh"
|
||||
#include "http/file_handler.hh"
|
||||
#include "apps/httpd/demo.json.hh"
|
||||
#include "http/api_docs.hh"
|
||||
|
||||
namespace bpo = boost::program_options;
|
||||
|
||||
using namespace httpd;
|
||||
|
||||
class handl : public httpd::handler_base {
|
||||
public:
|
||||
virtual future<std::unique_ptr<reply> > handle(const sstring& path,
|
||||
std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
|
||||
rep->_content = "hello";
|
||||
rep->done("html");
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
}
|
||||
};
|
||||
|
||||
void set_routes(routes& r) {
|
||||
function_handler* h1 = new function_handler([](const_req req) {
|
||||
return "hello";
|
||||
});
|
||||
function_handler* h2 = new function_handler([](std::unique_ptr<request> req) {
|
||||
return make_ready_future<json::json_return_type>("json-future");
|
||||
});
|
||||
r.add(operation_type::GET, url("/"), h1);
|
||||
r.add(operation_type::GET, url("/jf"), h2);
|
||||
r.add(operation_type::GET, url("/file").remainder("path"),
|
||||
new directory_handler("/"));
|
||||
demo_json::hello_world.set(r, [] (const_req req) {
|
||||
demo_json::my_object obj;
|
||||
obj.var1 = req.param.at("var1");
|
||||
obj.var2 = req.param.at("var2");
|
||||
demo_json::ns_hello_world::query_enum v = demo_json::ns_hello_world::str2query_enum(req.query_parameters.at("query_enum"));
|
||||
// This demonstrate enum conversion
|
||||
obj.enum_var = v;
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
int main(int ac, char** av) {
|
||||
app_template app;
|
||||
app.add_options()("port", bpo::value<uint16_t>()->default_value(10000),
|
||||
"HTTP Server port");
|
||||
return app.run(ac, av, [&] {
|
||||
auto&& config = app.configuration();
|
||||
uint16_t port = config["port"].as<uint16_t>();
|
||||
auto server = new http_server_control();
|
||||
auto rb = make_shared<api_registry_builder>("apps/httpd/");
|
||||
server->start().then([server] {
|
||||
return server->set_routes(set_routes);
|
||||
}).then([server, rb]{
|
||||
return server->set_routes([rb](routes& r){rb->set_api_doc(r);});
|
||||
}).then([server, rb]{
|
||||
return server->set_routes([rb](routes& r) {rb->register_function(r, "demo", "hello world application");});
|
||||
}).then([server, port] {
|
||||
return server->listen(port);
|
||||
}).then([server, port] {
|
||||
std::cout << "Seastar HTTP server listening on port " << port << " ...\n";
|
||||
engine().at_exit([server] {
|
||||
return server->stop();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#include "core/ragel.hh"
|
||||
#include "apps/memcached/memcached.hh"
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
%%{
|
||||
|
||||
machine memcache_ascii_protocol;
|
||||
|
||||
access _fsm_;
|
||||
|
||||
action mark {
|
||||
g.mark_start(p);
|
||||
}
|
||||
|
||||
action start_blob {
|
||||
g.mark_start(p);
|
||||
_size_left = _size;
|
||||
}
|
||||
|
||||
action advance_blob {
|
||||
auto len = std::min((uint32_t)(pe - p), _size_left);
|
||||
_size_left -= len;
|
||||
p += len;
|
||||
if (_size_left == 0) {
|
||||
_blob = str();
|
||||
p--;
|
||||
fret;
|
||||
}
|
||||
p--;
|
||||
}
|
||||
|
||||
crlf = '\r\n';
|
||||
sp = ' ';
|
||||
u32 = digit+ >{ _u32 = 0; } ${ _u32 *= 10; _u32 += fc - '0'; };
|
||||
u64 = digit+ >{ _u64 = 0; } ${ _u64 *= 10; _u64 += fc - '0'; };
|
||||
key = [^ ]+ >mark %{ _key = memcache::item_key(str()); };
|
||||
flags = digit+ >mark %{ _flags_str = str(); };
|
||||
expiration = u32 %{ _expiration = _u32; };
|
||||
size = u32 >mark %{ _size = _u32; _size_str = str(); };
|
||||
blob := any+ >start_blob $advance_blob;
|
||||
maybe_noreply = (sp "noreply" @{ _noreply = true; })? >{ _noreply = false; };
|
||||
maybe_expiration = (sp expiration)? >{ _expiration = 0; };
|
||||
version_field = u64 %{ _version = _u64; };
|
||||
|
||||
insertion_params = sp key sp flags sp expiration sp size maybe_noreply (crlf @{ fcall blob; } ) crlf;
|
||||
set = "set" insertion_params @{ _state = state::cmd_set; };
|
||||
add = "add" insertion_params @{ _state = state::cmd_add; };
|
||||
replace = "replace" insertion_params @{ _state = state::cmd_replace; };
|
||||
cas = "cas" sp key sp flags sp expiration sp size sp version_field maybe_noreply (crlf @{ fcall blob; } ) crlf @{ _state = state::cmd_cas; };
|
||||
get = "get" (sp key %{ _keys.emplace_back(std::move(_key)); })+ crlf @{ _state = state::cmd_get; };
|
||||
gets = "gets" (sp key %{ _keys.emplace_back(std::move(_key)); })+ crlf @{ _state = state::cmd_gets; };
|
||||
delete = "delete" sp key maybe_noreply crlf @{ _state = state::cmd_delete; };
|
||||
flush = "flush_all" maybe_expiration maybe_noreply crlf @{ _state = state::cmd_flush_all; };
|
||||
version = "version" crlf @{ _state = state::cmd_version; };
|
||||
stats = "stats" crlf @{ _state = state::cmd_stats; };
|
||||
stats_hash = "stats hash" crlf @{ _state = state::cmd_stats_hash; };
|
||||
incr = "incr" sp key sp u64 maybe_noreply crlf @{ _state = state::cmd_incr; };
|
||||
decr = "decr" sp key sp u64 maybe_noreply crlf @{ _state = state::cmd_decr; };
|
||||
main := (add | replace | set | get | gets | delete | flush | version | cas | stats | incr | decr
|
||||
| stats_hash) >eof{ _state = state::eof; };
|
||||
|
||||
prepush {
|
||||
prepush();
|
||||
}
|
||||
|
||||
postpop {
|
||||
postpop();
|
||||
}
|
||||
|
||||
}%%
|
||||
|
||||
class memcache_ascii_parser : public ragel_parser_base<memcache_ascii_parser> {
|
||||
%% write data nofinal noprefix;
|
||||
public:
|
||||
enum class state {
|
||||
error,
|
||||
eof,
|
||||
cmd_set,
|
||||
cmd_cas,
|
||||
cmd_add,
|
||||
cmd_replace,
|
||||
cmd_get,
|
||||
cmd_gets,
|
||||
cmd_delete,
|
||||
cmd_flush_all,
|
||||
cmd_version,
|
||||
cmd_stats,
|
||||
cmd_stats_hash,
|
||||
cmd_incr,
|
||||
cmd_decr,
|
||||
};
|
||||
state _state;
|
||||
uint32_t _u32;
|
||||
uint64_t _u64;
|
||||
memcache::item_key _key;
|
||||
sstring _flags_str;
|
||||
uint32_t _expiration;
|
||||
uint32_t _size;
|
||||
sstring _size_str;
|
||||
uint32_t _size_left;
|
||||
uint64_t _version;
|
||||
sstring _blob;
|
||||
bool _noreply;
|
||||
std::vector<memcache::item_key> _keys;
|
||||
public:
|
||||
void init() {
|
||||
init_base();
|
||||
_state = state::error;
|
||||
_keys.clear();
|
||||
%% write init;
|
||||
}
|
||||
|
||||
char* parse(char* p, char* pe, char* eof) {
|
||||
sstring_builder::guard g(_builder, p, pe);
|
||||
auto str = [this, &g, &p] { g.mark_end(p); return get_str(); };
|
||||
%% write exec;
|
||||
if (_state != state::error) {
|
||||
return p;
|
||||
}
|
||||
if (p != pe) {
|
||||
p = pe;
|
||||
return p;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
bool eof() const {
|
||||
return _state == state::eof;
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
#ifndef _MEMCACHED_HH
|
||||
#define _MEMCACHED_HH
|
||||
|
||||
#include "core/sstring.hh"
|
||||
|
||||
namespace memcache {
|
||||
|
||||
class item;
|
||||
class cache;
|
||||
|
||||
class item_key {
|
||||
private:
|
||||
sstring _key;
|
||||
size_t _hash;
|
||||
public:
|
||||
item_key() = default;
|
||||
item_key(item_key&) = default;
|
||||
item_key(sstring key)
|
||||
: _key(key)
|
||||
, _hash(std::hash<sstring>()(key))
|
||||
{}
|
||||
item_key(item_key&& other)
|
||||
: _key(std::move(other._key))
|
||||
, _hash(other._hash)
|
||||
{
|
||||
other._hash = 0;
|
||||
}
|
||||
size_t hash() const {
|
||||
return _hash;
|
||||
}
|
||||
const sstring& key() const {
|
||||
return _key;
|
||||
}
|
||||
bool operator==(const item_key& other) const {
|
||||
return other._hash == _hash && other._key == _key;
|
||||
}
|
||||
void operator=(item_key&& other) {
|
||||
_key = std::move(other._key);
|
||||
_hash = other._hash;
|
||||
other._hash = 0;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<memcache::item_key> {
|
||||
size_t operator()(const memcache::item_key& key) {
|
||||
return key.hash();
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace std */
|
||||
|
||||
#endif
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#include "core/ragel.hh"
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
struct http_response {
|
||||
sstring _version;
|
||||
std::unordered_map<sstring, sstring> _headers;
|
||||
};
|
||||
|
||||
%% machine http_response;
|
||||
|
||||
%%{
|
||||
|
||||
access _fsm_;
|
||||
|
||||
action mark {
|
||||
g.mark_start(p);
|
||||
}
|
||||
|
||||
action store_version {
|
||||
_rsp->_version = str();
|
||||
}
|
||||
|
||||
action store_field_name {
|
||||
_field_name = str();
|
||||
}
|
||||
|
||||
action store_value {
|
||||
_value = str();
|
||||
}
|
||||
|
||||
action assign_field {
|
||||
_rsp->_headers[_field_name] = std::move(_value);
|
||||
}
|
||||
|
||||
action extend_field {
|
||||
_rsp->_headers[_field_name] += sstring(" ") + std::move(_value);
|
||||
}
|
||||
|
||||
action done {
|
||||
done = true;
|
||||
fbreak;
|
||||
}
|
||||
|
||||
cr = '\r';
|
||||
lf = '\n';
|
||||
crlf = '\r\n';
|
||||
tchar = alpha | digit | '-' | '!' | '#' | '$' | '%' | '&' | '\'' | '*'
|
||||
| '+' | '.' | '^' | '_' | '`' | '|' | '~';
|
||||
|
||||
sp = ' ';
|
||||
ht = '\t';
|
||||
|
||||
sp_ht = sp | ht;
|
||||
|
||||
http_version = 'HTTP/' (digit '.' digit) >mark %store_version;
|
||||
|
||||
field = tchar+ >mark %store_field_name;
|
||||
value = any* >mark %store_value;
|
||||
start_line = http_version space digit digit digit space (any - cr - lf)* crlf;
|
||||
header_1st = (field sp_ht* ':' value :> crlf) %assign_field;
|
||||
header_cont = (sp_ht+ value sp_ht* crlf) %extend_field;
|
||||
header = header_1st header_cont*;
|
||||
main := start_line header* :> (crlf @done);
|
||||
|
||||
}%%
|
||||
|
||||
class http_response_parser : public ragel_parser_base<http_response_parser> {
|
||||
%% write data nofinal noprefix;
|
||||
public:
|
||||
enum class state {
|
||||
error,
|
||||
eof,
|
||||
done,
|
||||
};
|
||||
std::unique_ptr<http_response> _rsp;
|
||||
sstring _field_name;
|
||||
sstring _value;
|
||||
state _state;
|
||||
public:
|
||||
void init() {
|
||||
init_base();
|
||||
_rsp.reset(new http_response());
|
||||
_state = state::eof;
|
||||
%% write init;
|
||||
}
|
||||
char* parse(char* p, char* pe, char* eof) {
|
||||
sstring_builder::guard g(_builder, p, pe);
|
||||
auto str = [this, &g, &p] { g.mark_end(p); return get_str(); };
|
||||
bool done = false;
|
||||
if (p != pe) {
|
||||
_state = state::error;
|
||||
}
|
||||
%% write exec;
|
||||
if (!done) {
|
||||
p = nullptr;
|
||||
} else {
|
||||
_state = state::done;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
auto get_parsed_response() {
|
||||
return std::move(_rsp);
|
||||
}
|
||||
bool eof() const {
|
||||
return _state == state::eof;
|
||||
}
|
||||
};
|
||||
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#include "apps/seawreck/http_response_parser.hh"
|
||||
#include "core/print.hh"
|
||||
#include "core/reactor.hh"
|
||||
#include "core/app-template.hh"
|
||||
#include "core/future-util.hh"
|
||||
#include "core/distributed.hh"
|
||||
#include "core/semaphore.hh"
|
||||
#include "core/future-util.hh"
|
||||
#include <chrono>
|
||||
|
||||
template <typename... Args>
|
||||
void http_debug(const char* fmt, Args&&... args) {
|
||||
#if HTTP_DEBUG
|
||||
print(fmt, std::forward<Args>(args)...);
|
||||
#endif
|
||||
}
|
||||
|
||||
class http_client {
|
||||
private:
|
||||
unsigned _duration;
|
||||
unsigned _conn_per_core;
|
||||
unsigned _reqs_per_conn;
|
||||
std::vector<connected_socket> _sockets;
|
||||
semaphore _conn_connected{0};
|
||||
semaphore _conn_finished{0};
|
||||
timer<> _run_timer;
|
||||
bool _timer_based;
|
||||
bool _timer_done{false};
|
||||
uint64_t _total_reqs{0};
|
||||
public:
|
||||
http_client(unsigned duration, unsigned total_conn, unsigned reqs_per_conn)
|
||||
: _duration(duration)
|
||||
, _conn_per_core(total_conn / smp::count)
|
||||
, _reqs_per_conn(reqs_per_conn)
|
||||
, _run_timer([this] { _timer_done = true; })
|
||||
, _timer_based(reqs_per_conn == 0) {
|
||||
}
|
||||
|
||||
class connection {
|
||||
private:
|
||||
connected_socket _fd;
|
||||
input_stream<char> _read_buf;
|
||||
output_stream<char> _write_buf;
|
||||
http_response_parser _parser;
|
||||
http_client* _http_client;
|
||||
uint64_t _nr_done{0};
|
||||
public:
|
||||
connection(connected_socket&& fd, http_client* client)
|
||||
: _fd(std::move(fd))
|
||||
, _read_buf(_fd.input())
|
||||
, _write_buf(_fd.output())
|
||||
, _http_client(client){
|
||||
}
|
||||
|
||||
uint64_t nr_done() {
|
||||
return _nr_done;
|
||||
}
|
||||
|
||||
future<> do_req() {
|
||||
return _write_buf.write("GET / HTTP/1.1\r\nHost: 127.0.0.1:10000\r\n\r\n").then([this] {
|
||||
return _write_buf.flush();
|
||||
}).then([this] {
|
||||
_parser.init();
|
||||
return _read_buf.consume(_parser).then([this] {
|
||||
// Read HTTP response header first
|
||||
if (_parser.eof()) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
auto _rsp = _parser.get_parsed_response();
|
||||
auto it = _rsp->_headers.find("Content-Length");
|
||||
if (it == _rsp->_headers.end()) {
|
||||
print("Error: HTTP response does not contain: Content-Length\n");
|
||||
return make_ready_future<>();
|
||||
}
|
||||
auto content_len = std::stoi(it->second);
|
||||
http_debug("Content-Length = %d\n", content_len);
|
||||
// Read HTTP response body
|
||||
return _read_buf.read_exactly(content_len).then([this] (temporary_buffer<char> buf) {
|
||||
_nr_done++;
|
||||
http_debug("%s\n", buf.get());
|
||||
if (_http_client->done(_nr_done)) {
|
||||
return make_ready_future();
|
||||
} else {
|
||||
return do_req();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
future<uint64_t> total_reqs() {
|
||||
print("Requests on cpu %2d: %ld\n", engine().cpu_id(), _total_reqs);
|
||||
return make_ready_future<uint64_t>(_total_reqs);
|
||||
}
|
||||
|
||||
bool done(uint64_t nr_done) {
|
||||
if (_timer_based) {
|
||||
return _timer_done;
|
||||
} else {
|
||||
return nr_done >= _reqs_per_conn;
|
||||
}
|
||||
}
|
||||
|
||||
future<> connect(ipv4_addr server_addr) {
|
||||
// Establish all the TCP connections first
|
||||
for (unsigned i = 0; i < _conn_per_core; i++) {
|
||||
engine().net().connect(make_ipv4_address(server_addr)).then([this] (connected_socket fd) {
|
||||
_sockets.push_back(std::move(fd));
|
||||
http_debug("Established connection %6d on cpu %3d\n", _conn_connected.current(), engine().cpu_id());
|
||||
_conn_connected.signal();
|
||||
}).or_terminate();
|
||||
}
|
||||
return _conn_connected.wait(_conn_per_core);
|
||||
}
|
||||
|
||||
future<> run() {
|
||||
// All connected, start HTTP request
|
||||
http_debug("Established all %6d tcp connections on cpu %3d\n", _conn_per_core, engine().cpu_id());
|
||||
if (_timer_based) {
|
||||
_run_timer.arm(std::chrono::seconds(_duration));
|
||||
}
|
||||
for (auto&& fd : _sockets) {
|
||||
auto conn = new connection(std::move(fd), this);
|
||||
conn->do_req().then_wrapped([this, conn] (auto&& f) {
|
||||
http_debug("Finished connection %6d on cpu %3d\n", _conn_finished.current(), engine().cpu_id());
|
||||
_total_reqs += conn->nr_done();
|
||||
_conn_finished.signal();
|
||||
delete conn;
|
||||
try {
|
||||
f.get();
|
||||
} catch (std::exception& ex) {
|
||||
print("http request error: %s\n", ex.what());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// All finished
|
||||
return _conn_finished.wait(_conn_per_core);
|
||||
}
|
||||
future<> stop() {
|
||||
return make_ready_future();
|
||||
}
|
||||
};
|
||||
|
||||
namespace bpo = boost::program_options;
|
||||
|
||||
int main(int ac, char** av) {
|
||||
app_template app;
|
||||
app.add_options()
|
||||
("server,s", bpo::value<std::string>()->default_value("192.168.66.100:10000"), "Server address")
|
||||
("conn,c", bpo::value<unsigned>()->default_value(100), "total connections")
|
||||
("reqs,r", bpo::value<unsigned>()->default_value(0), "reqs per connection")
|
||||
("duration,d", bpo::value<unsigned>()->default_value(10), "duration of the test in seconds)");
|
||||
|
||||
return app.run(ac, av, [&app] {
|
||||
auto& config = app.configuration();
|
||||
auto server = config["server"].as<std::string>();
|
||||
auto reqs_per_conn = config["reqs"].as<unsigned>();
|
||||
auto total_conn= config["conn"].as<unsigned>();
|
||||
auto duration = config["duration"].as<unsigned>();
|
||||
|
||||
if (total_conn % smp::count != 0) {
|
||||
print("Error: conn needs to be n * cpu_nr\n");
|
||||
return engine().exit(0);
|
||||
}
|
||||
|
||||
auto http_clients = new distributed<http_client>;
|
||||
|
||||
// Start http requests on all the cores
|
||||
auto started = std::chrono::high_resolution_clock::now();
|
||||
print("========== http_client ============\n");
|
||||
print("Server: %s\n", server);
|
||||
print("Connections: %u\n", total_conn);
|
||||
print("Requests/connection: %s\n", reqs_per_conn == 0 ? "dynamic (timer based)" : std::to_string(reqs_per_conn));
|
||||
http_clients->start(std::ref(duration), std::ref(total_conn), std::ref(reqs_per_conn)).then([http_clients, started, server] {
|
||||
return http_clients->invoke_on_all(&http_client::connect, ipv4_addr{server});
|
||||
}).then([http_clients] {
|
||||
return http_clients->invoke_on_all(&http_client::run);
|
||||
}).then([http_clients] {
|
||||
return http_clients->map_reduce(adder<uint64_t>(), &http_client::total_reqs);
|
||||
}).then([http_clients, started] (auto total_reqs) {
|
||||
// All the http requests are finished
|
||||
auto finished = std::chrono::high_resolution_clock::now();
|
||||
auto elapsed = finished - started;
|
||||
auto secs = static_cast<double>(elapsed.count() / 1000000000.0);
|
||||
print("Total cpus: %u\n", smp::count);
|
||||
print("Total requests: %u\n", total_reqs);
|
||||
print("Total time: %f\n", secs);
|
||||
print("Requests/sec: %f\n", static_cast<double>(total_reqs) / secs);
|
||||
print("========== done ============\n");
|
||||
http_clients->stop().then([http_clients] {
|
||||
// FIXME: If we call engine().exit(0) here to exit when
|
||||
// requests are done. The tcp connection will not be closed
|
||||
// properly, becasue we exit too earily and the FIN packets are
|
||||
// not exchanged.
|
||||
delete http_clients;
|
||||
engine().exit(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
413
configure.py
413
configure.py
@@ -1,21 +1,4 @@
|
||||
#!/usr/bin/python3
|
||||
#
|
||||
# This file is open source software, licensed to you under the terms
|
||||
# of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
# distributed with this work for additional information regarding copyright
|
||||
# ownership. You may not use this file except in compliance with the License.
|
||||
#
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
import os, os.path, textwrap, argparse, sys, shlex, subprocess, tempfile, re
|
||||
|
||||
configure_args = str.join(' ', [shlex.quote(x) for x in sys.argv[1:]])
|
||||
@@ -195,58 +178,18 @@ urchin_tests = [
|
||||
'tests/urchin/network_topology_strategy_test',
|
||||
'tests/urchin/query_processor_test',
|
||||
'tests/urchin/batchlog_manager_test',
|
||||
]
|
||||
|
||||
tests = [
|
||||
'tests/fileiotest',
|
||||
'tests/directory_test',
|
||||
'tests/linecount',
|
||||
'tests/echotest',
|
||||
'tests/l3_test',
|
||||
'tests/ip_test',
|
||||
'tests/timertest',
|
||||
'tests/tcp_test',
|
||||
'tests/futures_test',
|
||||
'tests/foreign_ptr_test',
|
||||
'tests/smp_test',
|
||||
'tests/thread_test',
|
||||
'tests/thread_context_switch',
|
||||
'tests/udp_server',
|
||||
'tests/udp_client',
|
||||
'tests/blkdiscard_test',
|
||||
'tests/sstring_test',
|
||||
'tests/httpd',
|
||||
'tests/memcached/test_ascii_parser',
|
||||
'tests/tcp_server',
|
||||
'tests/tcp_client',
|
||||
'tests/allocator_test',
|
||||
'tests/output_stream_test',
|
||||
'tests/udp_zero_copy',
|
||||
'tests/shared_ptr_test',
|
||||
'tests/slab_test',
|
||||
'tests/fstream_test',
|
||||
'tests/distributed_test',
|
||||
'tests/rpc',
|
||||
'tests/semaphore_test',
|
||||
]
|
||||
|
||||
# urchin
|
||||
tests += [
|
||||
'tests/urchin/bytes_ostream_test',
|
||||
'tests/urchin/UUID_test',
|
||||
'tests/urchin/murmur_hash_test',
|
||||
]
|
||||
|
||||
apps = [
|
||||
'apps/httpd/httpd',
|
||||
'seastar',
|
||||
'apps/seawreck/seawreck',
|
||||
'apps/memcached/memcached',
|
||||
'scylla',
|
||||
]
|
||||
|
||||
tests += urchin_tests
|
||||
tests = urchin_tests
|
||||
|
||||
all_artifacts = apps + tests + ['libseastar.a', 'seastar.pc']
|
||||
all_artifacts = apps + tests
|
||||
|
||||
arg_parser = argparse.ArgumentParser('Configure seastar')
|
||||
arg_parser.add_argument('--static', dest = 'static', action = 'store_const', default = '',
|
||||
@@ -276,121 +219,8 @@ add_tristate(arg_parser, name = 'hwloc', dest = 'hwloc', help = 'hwloc support')
|
||||
add_tristate(arg_parser, name = 'xen', dest = 'xen', help = 'Xen support')
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
libnet = [
|
||||
'net/proxy.cc',
|
||||
'net/virtio.cc',
|
||||
'net/dpdk.cc',
|
||||
'net/ip.cc',
|
||||
'net/ethernet.cc',
|
||||
'net/arp.cc',
|
||||
'net/native-stack.cc',
|
||||
'net/ip_checksum.cc',
|
||||
'net/udp.cc',
|
||||
'net/tcp.cc',
|
||||
'net/dhcp.cc',
|
||||
]
|
||||
|
||||
core = [
|
||||
'core/reactor.cc',
|
||||
'core/fstream.cc',
|
||||
'core/posix.cc',
|
||||
'core/memory.cc',
|
||||
'core/resource.cc',
|
||||
'core/scollectd.cc',
|
||||
'core/app-template.cc',
|
||||
'core/thread.cc',
|
||||
'core/dpdk_rte.cc',
|
||||
'util/conversions.cc',
|
||||
'net/packet.cc',
|
||||
'net/posix-stack.cc',
|
||||
'net/net.cc',
|
||||
'rpc/rpc.cc',
|
||||
]
|
||||
|
||||
http = ['http/transformers.cc',
|
||||
'http/json_path.cc',
|
||||
'http/file_handler.cc',
|
||||
'http/common.cc',
|
||||
'http/routes.cc',
|
||||
'json/json_elements.cc',
|
||||
'json/formatter.cc',
|
||||
'http/matcher.cc',
|
||||
'http/mime_types.cc',
|
||||
'http/httpd.cc',
|
||||
'http/reply.cc',
|
||||
'http/request_parser.rl',
|
||||
'http/api_docs.cc',
|
||||
]
|
||||
|
||||
api = ['api/api.cc',
|
||||
'api/api-doc/storage_service.json',
|
||||
'api/storage_service.cc',
|
||||
'api/api-doc/commitlog.json',
|
||||
'api/commitlog.cc',
|
||||
'api/api-doc/gossiper.json',
|
||||
'api/gossiper.cc',
|
||||
'api/api-doc/failure_detector.json',
|
||||
'api/failure_detector.cc',
|
||||
'api/api-doc/column_family.json',
|
||||
'api/column_family.cc',
|
||||
'api/messaging_service.cc',
|
||||
'api/api-doc/messaging_service.json',
|
||||
'api/api-doc/storage_proxy.json',
|
||||
'api/storage_proxy.cc',
|
||||
'api/api-doc/cache_service.json',
|
||||
'api/cache_service.cc',
|
||||
'api/api-doc/collectd.json',
|
||||
'api/collectd.cc',
|
||||
'api/api-doc/endpoint_snitch_info.json',
|
||||
'api/endpoint_snitch.cc',
|
||||
'api/api-doc/compaction_manager.json',
|
||||
'api/compaction_manager.cc',
|
||||
'api/api-doc/hinted_handoff.json',
|
||||
'api/hinted_handoff.cc',
|
||||
]
|
||||
|
||||
boost_test_lib = [
|
||||
'tests/test-utils.cc',
|
||||
'tests/test_runner.cc',
|
||||
]
|
||||
|
||||
defines = []
|
||||
libs = '-laio -lboost_program_options -lboost_system -lboost_filesystem -lstdc++ -lm -lboost_unit_test_framework -lboost_thread -lcryptopp -lrt -lyaml-cpp -lboost_date_time'
|
||||
hwloc_libs = '-lhwloc -lnuma -lpciaccess -lxml2 -lz'
|
||||
urchin_libs = '-llz4 -lsnappy -lz'
|
||||
|
||||
libs = urchin_libs + ' ' + libs
|
||||
|
||||
xen_used = False
|
||||
def have_xen():
|
||||
source = '#include <stdint.h>\n'
|
||||
source += '#include <xen/xen.h>\n'
|
||||
source += '#include <xen/sys/evtchn.h>\n'
|
||||
source += '#include <xen/sys/gntdev.h>\n'
|
||||
source += '#include <xen/sys/gntalloc.h>\n'
|
||||
|
||||
return try_compile(compiler = args.cxx, source = source)
|
||||
|
||||
if apply_tristate(args.xen, test = have_xen,
|
||||
note = 'Note: xen-devel not installed. No Xen support.',
|
||||
missing = 'Error: required package xen-devel not installed.'):
|
||||
libs += ' -lxenstore'
|
||||
defines.append("HAVE_XEN")
|
||||
libnet += [ 'net/xenfront.cc' ]
|
||||
core += [
|
||||
'core/xen/xenstore.cc',
|
||||
'core/xen/gntalloc.cc',
|
||||
'core/xen/evtchn.cc',
|
||||
]
|
||||
xen_used=True
|
||||
|
||||
if xen_used and args.dpdk_target:
|
||||
print("Error: only xen or dpdk can be used, not both.")
|
||||
sys.exit(1)
|
||||
|
||||
memcache_base = [
|
||||
'apps/memcached/ascii.rl'
|
||||
] + libnet + core
|
||||
urchin_libs = '-llz4 -lsnappy -lz -lboost_thread -lcryptopp -lrt -lyaml-cpp -lboost_date_time'
|
||||
|
||||
cassandra_interface = Thrift(source = 'interface/cassandra.thrift', service = 'Cassandra')
|
||||
|
||||
@@ -532,74 +362,56 @@ urchin_core = (['database.cc',
|
||||
]
|
||||
+ [Antlr3Grammar('cql3/Cql.g')]
|
||||
+ [Thrift('interface/cassandra.thrift', 'Cassandra')]
|
||||
+ core + libnet)
|
||||
)
|
||||
|
||||
urchin_tests_dependencies = urchin_core + http + api + [
|
||||
api = ['api/api.cc',
|
||||
'api/api-doc/storage_service.json',
|
||||
'api/storage_service.cc',
|
||||
'api/api-doc/commitlog.json',
|
||||
'api/commitlog.cc',
|
||||
'api/api-doc/gossiper.json',
|
||||
'api/gossiper.cc',
|
||||
'api/api-doc/failure_detector.json',
|
||||
'api/failure_detector.cc',
|
||||
'api/api-doc/column_family.json',
|
||||
'api/column_family.cc',
|
||||
'api/messaging_service.cc',
|
||||
'api/api-doc/messaging_service.json',
|
||||
'api/api-doc/storage_proxy.json',
|
||||
'api/storage_proxy.cc',
|
||||
'api/api-doc/cache_service.json',
|
||||
'api/cache_service.cc',
|
||||
'api/api-doc/collectd.json',
|
||||
'api/collectd.cc',
|
||||
'api/api-doc/endpoint_snitch_info.json',
|
||||
'api/endpoint_snitch.cc',
|
||||
'api/api-doc/compaction_manager.json',
|
||||
'api/compaction_manager.cc',
|
||||
'api/api-doc/hinted_handoff.json',
|
||||
'api/hinted_handoff.cc',
|
||||
]
|
||||
|
||||
urchin_tests_dependencies = urchin_core + [
|
||||
'tests/urchin/cql_test_env.cc',
|
||||
'tests/urchin/cql_assertions.cc',
|
||||
'tests/urchin/result_set_assertions.cc',
|
||||
]
|
||||
|
||||
urchin_tests_seastar_deps = [
|
||||
'seastar/tests/test-utils.cc',
|
||||
'seastar/tests/test_runner.cc',
|
||||
]
|
||||
|
||||
deps = {
|
||||
'libseastar.a' : core + libnet,
|
||||
'seastar.pc': [],
|
||||
'seastar': ['main.cc'] + http + api + urchin_core,
|
||||
'apps/httpd/httpd': ['apps/httpd/demo.json', 'apps/httpd/main.cc'] + http + libnet + core,
|
||||
'apps/memcached/memcached': ['apps/memcached/memcache.cc'] + memcache_base,
|
||||
'tests/memcached/test_ascii_parser': ['tests/memcached/test_ascii_parser.cc'] + memcache_base + boost_test_lib,
|
||||
'tests/fileiotest': ['tests/fileiotest.cc'] + core + boost_test_lib,
|
||||
'tests/directory_test': ['tests/directory_test.cc'] + core,
|
||||
'tests/linecount': ['tests/linecount.cc'] + core,
|
||||
'tests/echotest': ['tests/echotest.cc'] + core + libnet,
|
||||
'tests/l3_test': ['tests/l3_test.cc'] + core + libnet,
|
||||
'tests/ip_test': ['tests/ip_test.cc'] + core + libnet,
|
||||
'tests/tcp_test': ['tests/tcp_test.cc'] + core + libnet,
|
||||
'tests/timertest': ['tests/timertest.cc'] + core,
|
||||
'tests/futures_test': ['tests/futures_test.cc'] + core + boost_test_lib,
|
||||
'tests/foreign_ptr_test': ['tests/foreign_ptr_test.cc'] + core + boost_test_lib,
|
||||
'tests/semaphore_test': ['tests/semaphore_test.cc'] + core + boost_test_lib,
|
||||
'tests/smp_test': ['tests/smp_test.cc'] + core,
|
||||
'tests/thread_test': ['tests/thread_test.cc'] + core + boost_test_lib,
|
||||
'tests/thread_context_switch': ['tests/thread_context_switch.cc'] + core,
|
||||
'tests/udp_server': ['tests/udp_server.cc'] + core + libnet,
|
||||
'tests/udp_client': ['tests/udp_client.cc'] + core + libnet,
|
||||
'tests/tcp_server': ['tests/tcp_server.cc'] + core + libnet,
|
||||
'tests/tcp_client': ['tests/tcp_client.cc'] + core + libnet,
|
||||
'apps/seawreck/seawreck': ['apps/seawreck/seawreck.cc', 'apps/seawreck/http_response_parser.rl'] + core + libnet,
|
||||
'tests/blkdiscard_test': ['tests/blkdiscard_test.cc'] + core,
|
||||
'tests/sstring_test': ['tests/sstring_test.cc'] + core,
|
||||
'tests/httpd': ['tests/httpd.cc'] + http + core + boost_test_lib,
|
||||
'tests/allocator_test': ['tests/allocator_test.cc', 'core/memory.cc', 'core/posix.cc'],
|
||||
'tests/output_stream_test': ['tests/output_stream_test.cc'] + core + libnet + boost_test_lib,
|
||||
'tests/udp_zero_copy': ['tests/udp_zero_copy.cc'] + core + libnet,
|
||||
'tests/shared_ptr_test': ['tests/shared_ptr_test.cc'] + core,
|
||||
'tests/slab_test': ['tests/slab_test.cc'] + core,
|
||||
'tests/fstream_test': ['tests/fstream_test.cc'] + core + boost_test_lib,
|
||||
'tests/distributed_test': ['tests/distributed_test.cc'] + core,
|
||||
'tests/rpc': ['tests/rpc.cc'] + core + libnet,
|
||||
'tests/urchin/gossiping_property_file_snitch_test': ['tests/urchin/gossiping_property_file_snitch_test.cc'] + urchin_core,
|
||||
'tests/urchin/network_topology_strategy_test': ['tests/urchin/network_topology_strategy_test.cc'] + urchin_core,
|
||||
'scylla': ['main.cc'] + urchin_core + api,
|
||||
}
|
||||
|
||||
for t in urchin_tests:
|
||||
deps[t] = urchin_tests_dependencies + [t + '.cc']
|
||||
if 'types_test' not in t and 'keys_test' not in t and 'partitioner_test' not in t and 'map_difference_test' not in t and 'frozen_mutation_test' not in t and 'perf_mutation' not in t and 'cartesian_product_test' not in t and 'perf_hash' not in t and 'perf_cql_parser' not in t and 'message' not in t and 'perf_simple_query' not in t and 'serialization' not in t and t != 'tests/urchin/gossip' and 'compound_test' not in t:
|
||||
deps[t] += urchin_tests_seastar_deps
|
||||
|
||||
deps['tests/urchin/mutation_test'] += boost_test_lib
|
||||
deps['tests/urchin/cql_query_test'] += boost_test_lib
|
||||
deps['tests/urchin/mutation_reader_test'] += boost_test_lib
|
||||
deps['tests/urchin/mutation_query_test'] += boost_test_lib
|
||||
deps['tests/urchin/commitlog_test'] += boost_test_lib
|
||||
deps['tests/urchin/config_test'] += boost_test_lib
|
||||
deps['tests/urchin/sstable_test'] += boost_test_lib + ['tests/urchin/sstable_datafile_test.cc']
|
||||
deps['tests/urchin/sstable_mutation_test'] += boost_test_lib
|
||||
deps['tests/urchin/hash_test'] += boost_test_lib
|
||||
deps['tests/urchin/serializer_test'] += boost_test_lib
|
||||
deps['tests/urchin/gossip_test'] += boost_test_lib
|
||||
deps['tests/urchin/gossiping_property_file_snitch_test'] += boost_test_lib
|
||||
deps['tests/urchin/network_topology_strategy_test'] += boost_test_lib
|
||||
deps['tests/urchin/row_cache_test'] += boost_test_lib
|
||||
deps['tests/urchin/query_processor_test'] += boost_test_lib
|
||||
deps['tests/urchin/batchlog_manager_test'] += boost_test_lib
|
||||
deps['tests/urchin/sstable_test'] += ['tests/urchin/sstable_datafile_test.cc']
|
||||
|
||||
deps['tests/urchin/bytes_ostream_test'] = ['tests/urchin/bytes_ostream_test.cc']
|
||||
deps['tests/urchin/UUID_test'] = ['utils/UUID_gen.cc', 'tests/urchin/UUID_test.cc']
|
||||
@@ -610,55 +422,6 @@ warnings = [
|
||||
'-Wno-maybe-uninitialized', # false positives on gcc 5
|
||||
]
|
||||
|
||||
# The "--with-osv=<path>" parameter is a shortcut for a bunch of other
|
||||
# settings:
|
||||
if args.with_osv:
|
||||
args.so = True
|
||||
args.hwloc = False
|
||||
args.user_cflags = (args.user_cflags +
|
||||
' -DDEFAULT_ALLOCATOR -fvisibility=default -DHAVE_OSV -I' +
|
||||
args.with_osv + ' -I' + args.with_osv + '/include -I' +
|
||||
args.with_osv + '/arch/x64')
|
||||
|
||||
if args.dpdk:
|
||||
subprocess.check_call('make -C dpdk RTE_OUTPUT=$PWD/build/dpdk/ config T=x86_64-native-linuxapp-gcc',
|
||||
shell = True)
|
||||
# adjust configutation to taste
|
||||
dotconfig = 'build/dpdk/.config'
|
||||
lines = open(dotconfig).readlines()
|
||||
def update(lines, vars):
|
||||
ret = []
|
||||
for line in lines:
|
||||
for var, val in vars.items():
|
||||
if line.startswith(var + '='):
|
||||
line = var + '=' + val + '\n'
|
||||
ret.append(line)
|
||||
return ret
|
||||
lines = update(lines, {'CONFIG_RTE_LIBRTE_PMD_BOND': 'n',
|
||||
'CONFIG_RTE_MBUF_SCATTER_GATHER': 'n',
|
||||
'CONFIG_RTE_LIBRTE_IP_FRAG': 'n',
|
||||
'CONFIG_RTE_APP_TEST': 'n',
|
||||
'CONFIG_RTE_TEST_PMD': 'n',
|
||||
'CONFIG_RTE_MBUF_REFCNT_ATOMIC': 'n',
|
||||
'CONFIG_RTE_MAX_MEMSEG': '8192',
|
||||
})
|
||||
open(dotconfig, 'w').writelines(lines)
|
||||
args.dpdk_target = 'build/dpdk'
|
||||
|
||||
if args.dpdk_target:
|
||||
args.user_cflags = (args.user_cflags +
|
||||
' -DHAVE_DPDK -I' + args.dpdk_target + '/include ' +
|
||||
dpdk_cflags(args.dpdk_target) +
|
||||
' -Wno-error=literal-suffix -Wno-literal-suffix -Wno-invalid-offsetof')
|
||||
libs += (' -L' + args.dpdk_target + '/lib ')
|
||||
if args.with_osv:
|
||||
libs += '-lintel_dpdk -lrt -lm -ldl'
|
||||
else:
|
||||
libs += '-Wl,--whole-archive -lrte_pmd_vmxnet3_uio -lrte_pmd_i40e -lrte_pmd_ixgbe -lrte_pmd_e1000 -lrte_pmd_ring -Wl,--no-whole-archive -lrte_distributor -lrte_pipeline -lrte_table -lrte_port -lrte_timer -lrte_hash -lrte_lpm -lrte_power -lrte_acl -lrte_meter -lrte_sched -lrte_kvargs -lrte_mbuf -lethdev -lrte_eal -lrte_malloc -lrte_mempool -lrte_ring -lrte_cmdline -lrte_cfgfile -lrt -lm -ldl'
|
||||
|
||||
args.user_cflags += " " + pkg_config("--cflags", "jsoncpp")
|
||||
libs += " " + pkg_config("--libs", "jsoncpp")
|
||||
|
||||
warnings = [w
|
||||
for w in warnings
|
||||
if warning_supported(warning = w, compiler = args.cxx)]
|
||||
@@ -667,16 +430,6 @@ warnings = ' '.join(warnings)
|
||||
|
||||
dbgflag = debug_flag(args.cxx) if args.debuginfo else ''
|
||||
|
||||
def have_hwloc():
|
||||
return try_compile(compiler = args.cxx, source = '#include <hwloc.h>\n#include <numa.h>')
|
||||
|
||||
if apply_tristate(args.hwloc, test = have_hwloc,
|
||||
note = 'Note: hwloc-devel/numactl-devel not installed. No NUMA support.',
|
||||
missing = 'Error: required packages hwloc-devel/numactl-devel not installed.'):
|
||||
libs += ' ' + hwloc_libs
|
||||
defines.append('HAVE_HWLOC')
|
||||
defines.append('HAVE_NUMA')
|
||||
|
||||
if args.so:
|
||||
args.pie = '-shared'
|
||||
args.fpie = '-fpic'
|
||||
@@ -697,13 +450,43 @@ link_pool_depth = max(int(total_memory / 7e9), 1)
|
||||
build_modes = modes if args.mode == 'all' else [args.mode]
|
||||
build_artifacts = all_artifacts if not args.artifacts else args.artifacts
|
||||
|
||||
dpdk_sources = []
|
||||
if args.dpdk:
|
||||
for root, dirs, files in os.walk('dpdk'):
|
||||
dpdk_sources += [os.path.join(root, file)
|
||||
for file in files
|
||||
if file.endswith('.h') or file.endswith('.c')]
|
||||
dpdk_sources = ' '.join(dpdk_sources)
|
||||
status = subprocess.call('./configure.py', cwd = 'seastar')
|
||||
|
||||
if status != 0:
|
||||
print('Seastar configuration failed')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
pc = { mode : 'build/{}/seastar.pc'.format(mode) for mode in build_modes }
|
||||
for ninja in ['ninja', 'ninja-build', 'true']:
|
||||
try:
|
||||
status = subprocess.call([ninja] + list(pc.values()), cwd = 'seastar')
|
||||
if status == 0:
|
||||
break
|
||||
except OSError as e:
|
||||
pass
|
||||
if ninja == 'true':
|
||||
print('Unable to create {}'.format(pc))
|
||||
sys.exit(1)
|
||||
|
||||
for mode in build_modes:
|
||||
cfg = dict([line.strip().split(': ', 1)
|
||||
for line in open('seastar/' + pc[mode])
|
||||
if ': ' in line])
|
||||
modes[mode]['seastar_cflags'] = cfg['Cflags']
|
||||
modes[mode]['seastar_libs'] = cfg['Libs']
|
||||
|
||||
def gen_seastar_deps():
|
||||
for root, dir, files in os.walk('seastar'):
|
||||
for f in files:
|
||||
if f.endswith('.cc') or f.endswith('.hh'):
|
||||
yield root + '/' + f
|
||||
|
||||
seastar_deps = ' '.join(gen_seastar_deps())
|
||||
|
||||
args.user_cflags += " " + pkg_config("--cflags", "jsoncpp")
|
||||
libs = "-lyaml-cpp -llz4 -lz -lsnappy " + pkg_config("--libs", "jsoncpp") + ' -lboost_filesystem'
|
||||
user_cflags = args.user_cflags
|
||||
|
||||
outdir = 'build'
|
||||
buildfile = 'build.ninja'
|
||||
@@ -720,9 +503,8 @@ with open(buildfile, 'w') as f:
|
||||
configure_args = {configure_args}
|
||||
builddir = {outdir}
|
||||
cxx = {cxx}
|
||||
# we disable _FORTIFY_SOURCE because it generates false positives with longjmp() (core/thread.cc)
|
||||
cxxflags = -std=gnu++1y {dbgflag} {fpie} -Wall -Werror -fvisibility=hidden -pthread -I. -U_FORTIFY_SOURCE {user_cflags} {warnings} {defines}
|
||||
ldflags = {dbgflag} -Wl,--no-as-needed {static} {pie} -fvisibility=hidden -pthread {user_ldflags}
|
||||
cxxflags = {user_cflags} {warnings} {defines}
|
||||
ldflags = {user_ldflags}
|
||||
libs = {libs}
|
||||
pool link_pool
|
||||
depth = {link_pool_depth}
|
||||
@@ -733,8 +515,11 @@ with open(buildfile, 'w') as f:
|
||||
command = echo -e $text > $out
|
||||
description = GEN $out
|
||||
rule swagger
|
||||
command = json/json2code.py -f $in -o $out
|
||||
command = seastar/json/json2code.py -f $in -o $out
|
||||
description = SWAGGER $out
|
||||
rule ninja
|
||||
command = {ninja} -C $subdir $target
|
||||
description = NINJA $out
|
||||
''').format(**globals()))
|
||||
if args.dpdk:
|
||||
f.write(textwrap.dedent('''\
|
||||
@@ -744,19 +529,14 @@ with open(buildfile, 'w') as f:
|
||||
''').format(**globals()))
|
||||
for mode in build_modes:
|
||||
modeval = modes[mode]
|
||||
if modeval['sanitize'] and not do_sanitize:
|
||||
print('Note: --static disables debug mode sanitizers')
|
||||
modeval['sanitize'] = ''
|
||||
modeval['sanitize_libs'] = ''
|
||||
f.write(textwrap.dedent('''\
|
||||
cxxflags_{mode} = {sanitize} {opt} -I $builddir/{mode}/gen
|
||||
libs_{mode} = {libs} {sanitize_libs}
|
||||
cxxflags_{mode} = -I. -I $builddir/{mode}/gen -I seastar
|
||||
rule cxx.{mode}
|
||||
command = $cxx -MMD -MT $out -MF $out.d $cxxflags $cxxflags_{mode} -c -o $out $in
|
||||
command = $cxx -MMD -MT $out -MF $out.d {seastar_cflags} $cxxflags $cxxflags_{mode} -c -o $out $in
|
||||
description = CXX $out
|
||||
depfile = $out.d
|
||||
rule link.{mode}
|
||||
command = $cxx $cxxflags_{mode} $ldflags -o $out $in $libs $libs_{mode}
|
||||
command = $cxx $cxxflags_{mode} $ldflags {seastar_libs} -o $out $in $libs $libs_{mode}
|
||||
description = LINK $out
|
||||
pool = link_pool
|
||||
rule link_stripped.{mode}
|
||||
@@ -807,18 +587,8 @@ with open(buildfile, 'w') as f:
|
||||
elif binary.endswith('.a'):
|
||||
f.write('build $builddir/{}/{}: ar.{} {}\n'.format(mode, binary, mode, str.join(' ', objs)))
|
||||
else:
|
||||
if binary.startswith('tests/'):
|
||||
# Our code's debugging information is huge, and multiplied
|
||||
# by many tests yields ridiculous amounts of disk space.
|
||||
# So we strip the tests by default; The user can very
|
||||
# quickly re-link the test unstripped by adding a "_g"
|
||||
# to the test name, e.g., "ninja build/release/testname_g"
|
||||
f.write('build $builddir/{}/{}: link_stripped.{} {}\n'.format(mode, binary, mode, str.join(' ', objs)))
|
||||
if has_thrift:
|
||||
f.write(' libs = -lthrift -lboost_system $libs\n')
|
||||
f.write('build $builddir/{}/{}_g: link.{} {}\n'.format(mode, binary, mode, str.join(' ', objs)))
|
||||
else:
|
||||
f.write('build $builddir/{}/{}: link.{} {} | {}\n'.format(mode, binary, mode, str.join(' ', objs), dpdk_deps))
|
||||
f.write('build $builddir/{}/{}: link.{} {} {}\n'.format(mode, binary, mode, str.join(' ', objs),
|
||||
'seastar/build/{}/libseastar.a'.format(mode)))
|
||||
if has_thrift:
|
||||
f.write(' libs = -lthrift -lboost_system $libs\n')
|
||||
for src in srcs:
|
||||
@@ -865,6 +635,9 @@ with open(buildfile, 'w') as f:
|
||||
for cc in grammar.sources('$builddir/{}/gen'.format(mode)):
|
||||
obj = cc.replace('.cpp', '.o')
|
||||
f.write('build {}: cxx.{} {}\n'.format(obj, mode, cc))
|
||||
f.write('build seastar/build/{}/libseastar.a: ninja {}\n'.format(mode, seastar_deps))
|
||||
f.write(' subdir = seastar\n')
|
||||
f.write(' target = build/{}/libseastar.a\n'.format(mode))
|
||||
f.write(textwrap.dedent('''\
|
||||
rule configure
|
||||
command = python3 configure.py $configure_args
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef ALIGN_HH_
|
||||
#define ALIGN_HH_
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
template <typename T>
|
||||
inline constexpr
|
||||
T align_up(T v, T align) {
|
||||
return (v + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline constexpr
|
||||
T* align_up(T* v, size_t align) {
|
||||
static_assert(sizeof(T) == 1, "align byte pointers only");
|
||||
return reinterpret_cast<T*>(align_up(reinterpret_cast<uintptr_t>(v), align));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline constexpr
|
||||
T align_down(T v, T align) {
|
||||
return v & ~(align - 1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline constexpr
|
||||
T* align_down(T* v, size_t align) {
|
||||
static_assert(sizeof(T) == 1, "align byte pointers only");
|
||||
return reinterpret_cast<T*>(align_down(reinterpret_cast<uintptr_t>(v), align));
|
||||
}
|
||||
|
||||
#endif /* ALIGN_HH_ */
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#include "app-template.hh"
|
||||
#include "core/reactor.hh"
|
||||
#include "core/scollectd.hh"
|
||||
#include "core/print.hh"
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace bpo = boost::program_options;
|
||||
|
||||
app_template::app_template()
|
||||
: _opts("App options") {
|
||||
_opts.add_options()
|
||||
("help,h", "show help message")
|
||||
;
|
||||
_opts.add(reactor::get_options_description());
|
||||
_opts.add(smp::get_options_description());
|
||||
_opts.add(scollectd::get_options_description());
|
||||
}
|
||||
|
||||
boost::program_options::options_description_easy_init
|
||||
app_template::add_options() {
|
||||
return _opts.add_options();
|
||||
}
|
||||
|
||||
void
|
||||
app_template::add_positional_options(std::initializer_list<positional_option> options) {
|
||||
for (auto&& o : options) {
|
||||
_opts.add(boost::make_shared<bpo::option_description>(o.name, o.value_semantic, o.help));
|
||||
_pos_opts.add(o.name, o.max_count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bpo::variables_map&
|
||||
app_template::configuration() {
|
||||
return *_configuration;
|
||||
}
|
||||
|
||||
int
|
||||
app_template::run(int ac, char ** av, std::function<void ()>&& func) {
|
||||
#ifdef DEBUG
|
||||
print("WARNING: debug mode. Not for benchmarking or production\n");
|
||||
#endif
|
||||
bpo::variables_map configuration;
|
||||
try {
|
||||
bpo::store(bpo::command_line_parser(ac, av)
|
||||
.options(_opts)
|
||||
.positional(_pos_opts)
|
||||
.run()
|
||||
, configuration);
|
||||
auto home = std::getenv("HOME");
|
||||
if (home) {
|
||||
std::ifstream ifs(std::string(home) + "/.config/seastar/seastar.conf");
|
||||
if (ifs) {
|
||||
bpo::store(bpo::parse_config_file(ifs, _opts), configuration);
|
||||
}
|
||||
}
|
||||
} catch (bpo::error& e) {
|
||||
print("error: %s\n\nTry --help.\n", e.what());
|
||||
return 2;
|
||||
}
|
||||
bpo::notify(configuration);
|
||||
if (configuration.count("help")) {
|
||||
std::cout << _opts << "\n";
|
||||
return 1;
|
||||
}
|
||||
smp::configure(configuration);
|
||||
_configuration = {std::move(configuration)};
|
||||
engine().when_started().then([this] {
|
||||
scollectd::configure( this->configuration());
|
||||
}).then(
|
||||
std::move(func)
|
||||
).then_wrapped([] (auto&& f) {
|
||||
try {
|
||||
f.get();
|
||||
} catch (std::exception& ex) {
|
||||
std::cout << "program failed with uncaught exception: " << ex.what() << "\n";
|
||||
engine().exit(1);
|
||||
}
|
||||
});
|
||||
auto exit_code = engine().run();
|
||||
smp::cleanup();
|
||||
return exit_code;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
#ifndef _APP_TEMPLATE_HH
|
||||
#define _APP_TEMPLATE_HH
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <functional>
|
||||
|
||||
class app_template {
|
||||
private:
|
||||
boost::program_options::options_description _opts;
|
||||
boost::program_options::positional_options_description _pos_opts;
|
||||
boost::optional<boost::program_options::variables_map> _configuration;
|
||||
public:
|
||||
struct positional_option {
|
||||
const char* name;
|
||||
const boost::program_options::value_semantic* value_semantic;
|
||||
const char* help;
|
||||
int max_count;
|
||||
};
|
||||
public:
|
||||
app_template();
|
||||
boost::program_options::options_description_easy_init add_options();
|
||||
void add_positional_options(std::initializer_list<positional_option> options);
|
||||
boost::program_options::variables_map& configuration();
|
||||
int run(int ac, char ** av, std::function<void ()>&& func);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef APPLY_HH_
|
||||
#define APPLY_HH_
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
template <typename Func, typename Args, typename IndexList>
|
||||
struct apply_helper;
|
||||
|
||||
template <typename Func, typename Tuple, size_t... I>
|
||||
struct apply_helper<Func, Tuple, std::index_sequence<I...>> {
|
||||
static auto apply(Func&& func, Tuple args) {
|
||||
return func(std::get<I>(std::forward<Tuple>(args))...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Func, typename... T>
|
||||
inline
|
||||
auto apply(Func&& func, std::tuple<T...>&& args) {
|
||||
using helper = apply_helper<Func, std::tuple<T...>&&, std::index_sequence_for<T...>>;
|
||||
return helper::apply(std::forward<Func>(func), std::move(args));
|
||||
}
|
||||
|
||||
template <typename Func, typename... T>
|
||||
inline
|
||||
auto apply(Func&& func, std::tuple<T...>& args) {
|
||||
using helper = apply_helper<Func, std::tuple<T...>&, std::index_sequence_for<T...>>;
|
||||
return helper::apply(std::forward<Func>(func), args);
|
||||
}
|
||||
|
||||
template <typename Func, typename... T>
|
||||
inline
|
||||
auto apply(Func&& func, const std::tuple<T...>& args) {
|
||||
using helper = apply_helper<Func, const std::tuple<T...>&, std::index_sequence_for<T...>>;
|
||||
return helper::apply(std::forward<Func>(func), args);
|
||||
}
|
||||
|
||||
#endif /* APPLY_HH_ */
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef ARRAY_MAP_HH_
|
||||
#define ARRAY_MAP_HH_
|
||||
|
||||
#include <array>
|
||||
|
||||
// unordered_map implemented as a simple array
|
||||
|
||||
template <typename Value, size_t Max>
|
||||
class array_map {
|
||||
std::array<Value, Max> _a {};
|
||||
public:
|
||||
array_map(std::initializer_list<std::pair<size_t, Value>> i) {
|
||||
for (auto kv : i) {
|
||||
_a[kv.first] = kv.second;
|
||||
}
|
||||
}
|
||||
Value& operator[](size_t key) { return _a[key]; }
|
||||
const Value& operator[](size_t key) const { return _a[key]; }
|
||||
|
||||
Value& at(size_t key) {
|
||||
if (key >= Max) {
|
||||
throw std::out_of_range(std::to_string(key) + " >= " + std::to_string(Max));
|
||||
}
|
||||
return _a[key];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /* ARRAY_MAP_HH_ */
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef BITOPS_HH_
|
||||
#define BITOPS_HH_
|
||||
|
||||
inline
|
||||
constexpr unsigned count_leading_zeros(unsigned x) {
|
||||
return __builtin_clz(x);
|
||||
}
|
||||
|
||||
inline
|
||||
constexpr unsigned count_leading_zeros(unsigned long x) {
|
||||
return __builtin_clzl(x);
|
||||
}
|
||||
|
||||
inline
|
||||
constexpr unsigned count_leading_zeros(unsigned long long x) {
|
||||
return __builtin_clzll(x);
|
||||
}
|
||||
|
||||
inline
|
||||
constexpr unsigned count_trailing_zeros(unsigned x) {
|
||||
return __builtin_ctz(x);
|
||||
}
|
||||
|
||||
inline
|
||||
constexpr unsigned count_trailing_zeros(unsigned long x) {
|
||||
return __builtin_ctzl(x);
|
||||
}
|
||||
|
||||
inline
|
||||
constexpr unsigned count_trailing_zeros(unsigned long long x) {
|
||||
return __builtin_ctzll(x);
|
||||
}
|
||||
|
||||
#endif /* BITOPS_HH_ */
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Imported from OSv:
|
||||
*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*
|
||||
* This work is open source software, licensed under the terms of the
|
||||
* BSD license as described in the LICENSE file in the top-level directory.
|
||||
*/
|
||||
|
||||
#ifndef __OSV_BITSET_ITER
|
||||
#define __OSV_BITSET_ITER
|
||||
|
||||
#include <bitset>
|
||||
#include <limits>
|
||||
|
||||
namespace bitsets {
|
||||
|
||||
static constexpr int ulong_bits = std::numeric_limits<unsigned long>::digits;
|
||||
|
||||
/**
|
||||
* Returns the number of leading zeros in value's binary representation.
|
||||
*
|
||||
* If value == 0 the result is undefied. If T is signed and value is negative
|
||||
* the result is undefined.
|
||||
*
|
||||
* The highest value that can be returned is std::numeric_limits<T>::digits - 1,
|
||||
* which is returned when value == 1.
|
||||
*/
|
||||
template<typename T>
|
||||
inline size_t count_leading_zeros(T value);
|
||||
|
||||
/**
|
||||
* Returns the number of trailing zeros in value's binary representation.
|
||||
*
|
||||
* If value == 0 the result is undefied. If T is signed and value is negative
|
||||
* the result is undefined.
|
||||
*
|
||||
* The highest value that can be returned is std::numeric_limits<T>::digits - 1.
|
||||
*/
|
||||
template<typename T>
|
||||
static inline size_t count_trailing_zeros(T value);
|
||||
|
||||
template<>
|
||||
inline size_t count_leading_zeros<unsigned long>(unsigned long value)
|
||||
{
|
||||
return __builtin_clzl(value);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline size_t count_leading_zeros<long>(long value)
|
||||
{
|
||||
return __builtin_clzl((unsigned long)value) - 1;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline
|
||||
size_t count_trailing_zeros<unsigned long>(unsigned long value)
|
||||
{
|
||||
return __builtin_ctzl(value);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline
|
||||
size_t count_trailing_zeros<long>(long value)
|
||||
{
|
||||
return __builtin_ctzl((unsigned long)value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the first set bit.
|
||||
* Result is undefined if bitset.any() == false.
|
||||
*/
|
||||
template<size_t N>
|
||||
static inline size_t get_first_set(const std::bitset<N>& bitset)
|
||||
{
|
||||
static_assert(N <= ulong_bits, "bitset too large");
|
||||
return count_trailing_zeros(bitset.to_ulong());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the last set bit in the bitset.
|
||||
* Result is undefined if bitset.any() == false.
|
||||
*/
|
||||
template<size_t N>
|
||||
static inline size_t get_last_set(const std::bitset<N>& bitset)
|
||||
{
|
||||
static_assert(N <= ulong_bits, "bitset too large");
|
||||
return ulong_bits - 1 - count_leading_zeros(bitset.to_ulong());
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
class set_iterator : public std::iterator<std::input_iterator_tag, int>
|
||||
{
|
||||
private:
|
||||
void advance()
|
||||
{
|
||||
if (_bitset.none()) {
|
||||
_index = -1;
|
||||
} else {
|
||||
auto shift = get_first_set(_bitset) + 1;
|
||||
_index += shift;
|
||||
_bitset >>= shift;
|
||||
}
|
||||
}
|
||||
public:
|
||||
set_iterator(std::bitset<N> bitset, int offset = 0)
|
||||
: _bitset(bitset)
|
||||
, _index(offset - 1)
|
||||
{
|
||||
static_assert(N <= ulong_bits, "This implementation is inefficient for large bitsets");
|
||||
_bitset >>= offset;
|
||||
advance();
|
||||
}
|
||||
|
||||
void operator++()
|
||||
{
|
||||
advance();
|
||||
}
|
||||
|
||||
int operator*() const
|
||||
{
|
||||
return _index;
|
||||
}
|
||||
|
||||
bool operator==(const set_iterator& other) const
|
||||
{
|
||||
return _index == other._index;
|
||||
}
|
||||
|
||||
bool operator!=(const set_iterator& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
private:
|
||||
std::bitset<N> _bitset;
|
||||
int _index;
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
class set_range
|
||||
{
|
||||
public:
|
||||
using iterator = set_iterator<N>;
|
||||
using value_type = int;
|
||||
|
||||
set_range(std::bitset<N> bitset, int offset = 0)
|
||||
: _bitset(bitset)
|
||||
, _offset(offset)
|
||||
{
|
||||
}
|
||||
|
||||
iterator begin() const { return iterator(_bitset, _offset); }
|
||||
iterator end() const { return iterator(0); }
|
||||
private:
|
||||
std::bitset<N> _bitset;
|
||||
int _offset;
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
static inline set_range<N> for_each_set(std::bitset<N> bitset, int offset = 0)
|
||||
{
|
||||
return set_range<N>(bitset, offset);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,383 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef CIRCULAR_BUFFER_HH_
|
||||
#define CIRCULAR_BUFFER_HH_
|
||||
|
||||
// A growable double-ended queue container that can be efficiently
|
||||
// extended (and shrunk) from both ends. Implementation is a single
|
||||
// storage vector.
|
||||
//
|
||||
// Similar to libstdc++'s std::deque, except that it uses a single level
|
||||
// store, and so is more efficient for simple stored items.
|
||||
// Similar to boost::circular_buffer_space_optimized, except it uses
|
||||
// uninitialized storage for unoccupied elements (and thus move/copy
|
||||
// constructors instead of move/copy assignments, which are less efficient).
|
||||
|
||||
#include "transfer.hh"
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
|
||||
template <typename T, typename Alloc = std::allocator<T>>
|
||||
class circular_buffer {
|
||||
struct impl : Alloc {
|
||||
T* storage = nullptr;
|
||||
// begin, end interpreted (mod capacity)
|
||||
size_t begin = 0;
|
||||
size_t end = 0;
|
||||
size_t capacity = 0;
|
||||
};
|
||||
impl _impl;
|
||||
public:
|
||||
using value_type = T;
|
||||
using size_type = size_t;
|
||||
using reference = T&;
|
||||
using pointer = T*;
|
||||
using const_reference = const T&;
|
||||
using const_pointer = const T*;
|
||||
public:
|
||||
circular_buffer() = default;
|
||||
circular_buffer(circular_buffer&& X);
|
||||
circular_buffer(const circular_buffer& X) = delete;
|
||||
~circular_buffer();
|
||||
circular_buffer& operator=(const circular_buffer&) = delete;
|
||||
circular_buffer& operator=(circular_buffer&&) = delete;
|
||||
void push_front(const T& data);
|
||||
void push_front(T&& data);
|
||||
template <typename... A>
|
||||
void emplace_front(A&&... args);
|
||||
void push_back(const T& data);
|
||||
void push_back(T&& data);
|
||||
template <typename... A>
|
||||
void emplace_back(A&&... args);
|
||||
T& front();
|
||||
T& back();
|
||||
void pop_front();
|
||||
void pop_back();
|
||||
bool empty() const;
|
||||
size_t size() const;
|
||||
size_t capacity() const;
|
||||
T& operator[](size_t idx);
|
||||
template <typename Func>
|
||||
void for_each(Func func);
|
||||
// access an element, may return wrong or destroyed element
|
||||
// only useful if you do not rely on data accuracy (e.g. prefetch)
|
||||
T& access_element_unsafe(size_t idx);
|
||||
private:
|
||||
void expand();
|
||||
void maybe_expand(size_t nr = 1);
|
||||
size_t mask(size_t idx) const;
|
||||
|
||||
template<typename CB, typename ValueType>
|
||||
struct cbiterator : std::iterator<std::random_access_iterator_tag, ValueType> {
|
||||
typedef std::iterator<std::random_access_iterator_tag, ValueType> super_t;
|
||||
|
||||
ValueType& operator*() const { return cb->_impl.storage[cb->mask(idx)]; }
|
||||
ValueType* operator->() const { return &cb->_impl.storage[cb->mask(idx)]; }
|
||||
// prefix
|
||||
cbiterator<CB, ValueType>& operator++() {
|
||||
idx++;
|
||||
return *this;
|
||||
}
|
||||
// postfix
|
||||
cbiterator<CB, ValueType> operator++(int unused) {
|
||||
auto v = *this;
|
||||
idx++;
|
||||
return v;
|
||||
}
|
||||
// prefix
|
||||
cbiterator<CB, ValueType>& operator--() {
|
||||
idx--;
|
||||
return *this;
|
||||
}
|
||||
// postfix
|
||||
cbiterator<CB, ValueType> operator--(int unused) {
|
||||
auto v = *this;
|
||||
idx--;
|
||||
return v;
|
||||
}
|
||||
cbiterator<CB, ValueType> operator+(typename super_t::difference_type n) const {
|
||||
return cbiterator<CB, ValueType>(cb, idx + n);
|
||||
}
|
||||
cbiterator<CB, ValueType> operator-(typename super_t::difference_type n) const {
|
||||
return cbiterator<CB, ValueType>(cb, idx - n);
|
||||
}
|
||||
cbiterator<CB, ValueType>& operator+=(typename super_t::difference_type n) {
|
||||
idx += n;
|
||||
return *this;
|
||||
}
|
||||
cbiterator<CB, ValueType>& operator-=(typename super_t::difference_type n) {
|
||||
idx -= n;
|
||||
return *this;
|
||||
}
|
||||
bool operator==(const cbiterator<CB, ValueType>& rhs) const {
|
||||
return idx == rhs.idx;
|
||||
}
|
||||
bool operator!=(const cbiterator<CB, ValueType>& rhs) const {
|
||||
return idx != rhs.idx;
|
||||
}
|
||||
bool operator<(const cbiterator<CB, ValueType>& rhs) const {
|
||||
return idx < rhs.idx;
|
||||
}
|
||||
bool operator>(const cbiterator<CB, ValueType>& rhs) const {
|
||||
return idx > rhs.idx;
|
||||
}
|
||||
bool operator>=(const cbiterator<CB, ValueType>& rhs) const {
|
||||
return idx >= rhs.idx;
|
||||
}
|
||||
bool operator<=(const cbiterator<CB, ValueType>& rhs) const {
|
||||
return idx <= rhs.idx;
|
||||
}
|
||||
typename super_t::difference_type operator-(const cbiterator<CB, ValueType>& rhs) const {
|
||||
return idx - rhs.idx;
|
||||
}
|
||||
private:
|
||||
CB* cb;
|
||||
size_t idx;
|
||||
cbiterator<CB, ValueType>(CB* b, size_t i) : cb(b), idx(i) {}
|
||||
friend class circular_buffer;
|
||||
};
|
||||
friend class iterator;
|
||||
|
||||
public:
|
||||
typedef cbiterator<circular_buffer, T> iterator;
|
||||
typedef cbiterator<const circular_buffer, const T> const_iterator;
|
||||
|
||||
iterator begin() {
|
||||
return iterator(this, _impl.begin);
|
||||
}
|
||||
const_iterator begin() const {
|
||||
return const_iterator(this, _impl.begin);
|
||||
}
|
||||
iterator end() {
|
||||
return iterator(this, _impl.end);
|
||||
}
|
||||
const_iterator end() const {
|
||||
return const_iterator(this, _impl.end);
|
||||
}
|
||||
const_iterator cbegin() const {
|
||||
return const_iterator(this, _impl.begin);
|
||||
}
|
||||
const_iterator cend() const {
|
||||
return const_iterator(this, _impl.end);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
size_t
|
||||
circular_buffer<T, Alloc>::mask(size_t idx) const {
|
||||
return idx & (_impl.capacity - 1);
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
bool
|
||||
circular_buffer<T, Alloc>::empty() const {
|
||||
return _impl.begin == _impl.end;
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
size_t
|
||||
circular_buffer<T, Alloc>::size() const {
|
||||
return _impl.end - _impl.begin;
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
size_t
|
||||
circular_buffer<T, Alloc>::capacity() const {
|
||||
return _impl.capacity;
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
circular_buffer<T, Alloc>::circular_buffer(circular_buffer&& x)
|
||||
: _impl(std::move(x._impl)) {
|
||||
x._impl = {};
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
template <typename Func>
|
||||
inline
|
||||
void
|
||||
circular_buffer<T, Alloc>::for_each(Func func) {
|
||||
auto s = _impl.storage;
|
||||
auto m = _impl.capacity - 1;
|
||||
for (auto i = _impl.begin; i != _impl.end; ++i) {
|
||||
func(s[i & m]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
circular_buffer<T, Alloc>::~circular_buffer() {
|
||||
for_each([this] (T& obj) {
|
||||
_impl.destroy(&obj);
|
||||
});
|
||||
_impl.deallocate(_impl.storage, _impl.capacity);
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
void
|
||||
circular_buffer<T, Alloc>::expand() {
|
||||
auto new_cap = std::max<size_t>(_impl.capacity * 2, 1);
|
||||
auto new_storage = _impl.allocate(new_cap);
|
||||
auto p = new_storage;
|
||||
try {
|
||||
for_each([this, &p] (T& obj) {
|
||||
transfer_pass1(_impl, &obj, p++);
|
||||
});
|
||||
} catch (...) {
|
||||
while (p != new_storage) {
|
||||
_impl.destroy(--p);
|
||||
}
|
||||
_impl.deallocate(new_storage, new_cap);
|
||||
throw;
|
||||
}
|
||||
p = new_storage;
|
||||
for_each([this, &p] (T& obj) {
|
||||
transfer_pass2(_impl, &obj, p++);
|
||||
});
|
||||
std::swap(_impl.storage, new_storage);
|
||||
std::swap(_impl.capacity, new_cap);
|
||||
_impl.begin = 0;
|
||||
_impl.end = p - _impl.storage;
|
||||
_impl.deallocate(new_storage, new_cap);
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
void
|
||||
circular_buffer<T, Alloc>::maybe_expand(size_t nr) {
|
||||
if (_impl.end - _impl.begin + nr > _impl.capacity) {
|
||||
expand();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
void
|
||||
circular_buffer<T, Alloc>::push_front(const T& data) {
|
||||
maybe_expand();
|
||||
auto p = &_impl.storage[mask(_impl.begin - 1)];
|
||||
_impl.construct(p, data);
|
||||
--_impl.begin;
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
void
|
||||
circular_buffer<T, Alloc>::push_front(T&& data) {
|
||||
maybe_expand();
|
||||
auto p = &_impl.storage[mask(_impl.begin - 1)];
|
||||
_impl.construct(p, std::move(data));
|
||||
--_impl.begin;
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
template <typename... Args>
|
||||
inline
|
||||
void
|
||||
circular_buffer<T, Alloc>::emplace_front(Args&&... args) {
|
||||
maybe_expand();
|
||||
auto p = &_impl.storage[mask(_impl.begin - 1)];
|
||||
_impl.construct(p, std::forward<Args>(args)...);
|
||||
--_impl.begin;
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
void
|
||||
circular_buffer<T, Alloc>::push_back(const T& data) {
|
||||
maybe_expand();
|
||||
auto p = &_impl.storage[mask(_impl.end)];
|
||||
_impl.construct(p, data);
|
||||
++_impl.end;
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
void
|
||||
circular_buffer<T, Alloc>::push_back(T&& data) {
|
||||
maybe_expand();
|
||||
auto p = &_impl.storage[mask(_impl.end)];
|
||||
_impl.construct(p, std::move(data));
|
||||
++_impl.end;
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
template <typename... Args>
|
||||
inline
|
||||
void
|
||||
circular_buffer<T, Alloc>::emplace_back(Args&&... args) {
|
||||
maybe_expand();
|
||||
auto p = &_impl.storage[mask(_impl.end)];
|
||||
_impl.construct(p, std::forward<Args>(args)...);
|
||||
++_impl.end;
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
T&
|
||||
circular_buffer<T, Alloc>::front() {
|
||||
return _impl.storage[mask(_impl.begin)];
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
T&
|
||||
circular_buffer<T, Alloc>::back() {
|
||||
return _impl.storage[mask(_impl.end - 1)];
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
void
|
||||
circular_buffer<T, Alloc>::pop_front() {
|
||||
_impl.destroy(&front());
|
||||
++_impl.begin;
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
void
|
||||
circular_buffer<T, Alloc>::pop_back() {
|
||||
_impl.destroy(&back());
|
||||
--_impl.end;
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
T&
|
||||
circular_buffer<T, Alloc>::operator[](size_t idx) {
|
||||
return _impl.storage[mask(_impl.begin + idx)];
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
T&
|
||||
circular_buffer<T, Alloc>::access_element_unsafe(size_t idx) {
|
||||
return _impl.storage[mask(_impl.begin + idx)];
|
||||
}
|
||||
|
||||
#endif /* CIRCULAR_BUFFER_HH_ */
|
||||
275
core/deleter.hh
275
core/deleter.hh
@@ -1,275 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef DELETER_HH_
|
||||
#define DELETER_HH_
|
||||
|
||||
#include <memory>
|
||||
#include <cstdlib>
|
||||
#include <assert.h>
|
||||
#include <type_traits>
|
||||
|
||||
/// \addtogroup memory-module
|
||||
/// @{
|
||||
|
||||
/// Provides a mechanism for managing the lifetime of a buffer.
|
||||
///
|
||||
/// A \c deleter is an object that is used to inform the consumer
|
||||
/// of some buffer (not referenced by the deleter itself) how to
|
||||
/// delete the buffer. This can be by calling an arbitrary function
|
||||
/// or destroying an object carried by the deleter. Examples of
|
||||
/// a deleter's encapsulated actions are:
|
||||
///
|
||||
/// - calling \c std::free(p) on some captured pointer, p
|
||||
/// - calling \c delete \c p on some captured pointer, p
|
||||
/// - decrementing a reference count somewhere
|
||||
///
|
||||
/// A deleter performs its action from its destructor.
|
||||
class deleter final {
|
||||
public:
|
||||
/// \cond internal
|
||||
struct impl;
|
||||
struct raw_object_tag {};
|
||||
/// \endcond
|
||||
private:
|
||||
// if bit 0 set, point to object to be freed directly.
|
||||
impl* _impl = nullptr;
|
||||
public:
|
||||
/// Constructs an empty deleter that does nothing in its destructor.
|
||||
deleter() = default;
|
||||
deleter(const deleter&) = delete;
|
||||
/// Moves a deleter.
|
||||
deleter(deleter&& x) : _impl(x._impl) { x._impl = nullptr; }
|
||||
/// \cond internal
|
||||
explicit deleter(impl* i) : _impl(i) {}
|
||||
deleter(raw_object_tag tag, void* object)
|
||||
: _impl(from_raw_object(object)) {}
|
||||
/// \endcond
|
||||
/// Destroys the deleter and carries out the encapsulated action.
|
||||
~deleter();
|
||||
deleter& operator=(deleter&& x);
|
||||
deleter& operator=(deleter&) = delete;
|
||||
/// Performs a sharing operation. The encapsulated action will only
|
||||
/// be carried out after both the original deleter and the returned
|
||||
/// deleter are both destroyed.
|
||||
///
|
||||
/// \return a deleter with the same encapsulated action as this one.
|
||||
deleter share();
|
||||
/// Checks whether the deleter has an associated action.
|
||||
explicit operator bool() const { return bool(_impl); }
|
||||
/// \cond internal
|
||||
void reset(impl* i) {
|
||||
this->~deleter();
|
||||
new (this) deleter(i);
|
||||
}
|
||||
/// \endcond
|
||||
/// Appends another deleter to this deleter. When this deleter is
|
||||
/// destroyed, both encapsulated actions will be carried out.
|
||||
void append(deleter d);
|
||||
private:
|
||||
static bool is_raw_object(impl* i) {
|
||||
auto x = reinterpret_cast<uintptr_t>(i);
|
||||
return x & 1;
|
||||
}
|
||||
bool is_raw_object() const {
|
||||
return is_raw_object(_impl);
|
||||
}
|
||||
static void* to_raw_object(impl* i) {
|
||||
auto x = reinterpret_cast<uintptr_t>(i);
|
||||
return reinterpret_cast<void*>(x & ~uintptr_t(1));
|
||||
}
|
||||
void* to_raw_object() const {
|
||||
return to_raw_object(_impl);
|
||||
}
|
||||
impl* from_raw_object(void* object) {
|
||||
auto x = reinterpret_cast<uintptr_t>(object);
|
||||
return reinterpret_cast<impl*>(x | 1);
|
||||
}
|
||||
};
|
||||
|
||||
/// \cond internal
|
||||
struct deleter::impl {
|
||||
unsigned refs = 1;
|
||||
deleter next;
|
||||
impl(deleter next) : next(std::move(next)) {}
|
||||
virtual ~impl() {}
|
||||
};
|
||||
/// \endcond
|
||||
|
||||
inline
|
||||
deleter::~deleter() {
|
||||
if (is_raw_object()) {
|
||||
std::free(to_raw_object());
|
||||
return;
|
||||
}
|
||||
if (_impl && --_impl->refs == 0) {
|
||||
delete _impl;
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
deleter& deleter::operator=(deleter&& x) {
|
||||
if (this != &x) {
|
||||
this->~deleter();
|
||||
new (this) deleter(std::move(x));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// \cond internal
|
||||
template <typename Deleter>
|
||||
struct lambda_deleter_impl final : deleter::impl {
|
||||
Deleter del;
|
||||
lambda_deleter_impl(deleter next, Deleter&& del)
|
||||
: impl(std::move(next)), del(std::move(del)) {}
|
||||
virtual ~lambda_deleter_impl() override { del(); }
|
||||
};
|
||||
|
||||
template <typename Object>
|
||||
struct object_deleter_impl final : deleter::impl {
|
||||
Object obj;
|
||||
object_deleter_impl(deleter next, Object&& obj)
|
||||
: impl(std::move(next)), obj(std::move(obj)) {}
|
||||
};
|
||||
|
||||
template <typename Object>
|
||||
inline
|
||||
object_deleter_impl<Object>* make_object_deleter_impl(deleter next, Object obj) {
|
||||
return new object_deleter_impl<Object>(std::move(next), std::move(obj));
|
||||
}
|
||||
/// \endcond
|
||||
|
||||
/// Makes a \ref deleter that encapsulates the action of
|
||||
/// destroying an object, as well as running another deleter. The input
|
||||
/// object is moved to the deleter, and destroyed when the deleter is destroyed.
|
||||
///
|
||||
/// \param d deleter that will become part of the new deleter's encapsulated action
|
||||
/// \param o object whose destructor becomes part of the new deleter's encapsulated action
|
||||
/// \related deleter
|
||||
template <typename Object>
|
||||
deleter
|
||||
make_deleter(deleter next, Object o) {
|
||||
return deleter(new lambda_deleter_impl<Object>(std::move(next), std::move(o)));
|
||||
}
|
||||
|
||||
/// Makes a \ref deleter that encapsulates the action of destroying an object. The input
|
||||
/// object is moved to the deleter, and destroyed when the deleter is destroyed.
|
||||
///
|
||||
/// \param o object whose destructor becomes the new deleter's encapsulated action
|
||||
/// \related deleter
|
||||
template <typename Object>
|
||||
deleter
|
||||
make_deleter(Object o) {
|
||||
return make_deleter(deleter(), std::move(o));
|
||||
}
|
||||
|
||||
/// \cond internal
|
||||
struct free_deleter_impl final : deleter::impl {
|
||||
void* obj;
|
||||
free_deleter_impl(void* obj) : impl(deleter()), obj(obj) {}
|
||||
virtual ~free_deleter_impl() override { std::free(obj); }
|
||||
};
|
||||
/// \endcond
|
||||
|
||||
inline
|
||||
deleter
|
||||
deleter::share() {
|
||||
if (!_impl) {
|
||||
return deleter();
|
||||
}
|
||||
if (is_raw_object()) {
|
||||
_impl = new free_deleter_impl(to_raw_object());
|
||||
}
|
||||
++_impl->refs;
|
||||
return deleter(_impl);
|
||||
}
|
||||
|
||||
// Appends 'd' to the chain of deleters. Avoids allocation if possible. For
|
||||
// performance reasons the current chain should be shorter and 'd' should be
|
||||
// longer.
|
||||
inline
|
||||
void deleter::append(deleter d) {
|
||||
if (!d._impl) {
|
||||
return;
|
||||
}
|
||||
impl* next_impl = _impl;
|
||||
deleter* next_d = this;
|
||||
while (next_impl) {
|
||||
assert(next_impl != d._impl);
|
||||
if (is_raw_object(next_impl)) {
|
||||
next_d->_impl = next_impl = new free_deleter_impl(to_raw_object(next_impl));
|
||||
}
|
||||
if (next_impl->refs != 1) {
|
||||
next_d->_impl = next_impl = make_object_deleter_impl(std::move(next_impl->next), deleter(next_impl));
|
||||
}
|
||||
next_d = &next_impl->next;
|
||||
next_impl = next_d->_impl;
|
||||
}
|
||||
next_d->_impl = d._impl;
|
||||
d._impl = nullptr;
|
||||
}
|
||||
|
||||
/// Makes a deleter that calls \c std::free() when it is destroyed.
|
||||
///
|
||||
/// \param obj object to free.
|
||||
/// \related deleter
|
||||
inline
|
||||
deleter
|
||||
make_free_deleter(void* obj) {
|
||||
if (!obj) {
|
||||
return deleter();
|
||||
}
|
||||
return deleter(deleter::raw_object_tag(), obj);
|
||||
}
|
||||
|
||||
/// Makes a deleter that calls \c std::free() when it is destroyed, as well
|
||||
/// as invoking the encapsulated action of another deleter.
|
||||
///
|
||||
/// \param d deleter to invoke.
|
||||
/// \param obj object to free.
|
||||
/// \related deleter
|
||||
inline
|
||||
deleter
|
||||
make_free_deleter(deleter next, void* obj) {
|
||||
return make_deleter(std::move(next), [obj] () mutable { std::free(obj); });
|
||||
}
|
||||
|
||||
/// \see make_deleter(Object)
|
||||
/// \related deleter
|
||||
template <typename T>
|
||||
inline
|
||||
deleter
|
||||
make_object_deleter(T&& obj) {
|
||||
return deleter{make_object_deleter_impl(deleter(), std::move(obj))};
|
||||
}
|
||||
|
||||
/// \see make_deleter(deleter, Object)
|
||||
/// \related deleter
|
||||
template <typename T>
|
||||
inline
|
||||
deleter
|
||||
make_object_deleter(deleter d, T&& obj) {
|
||||
return deleter{make_object_deleter_impl(std::move(d), std::move(obj))};
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
#endif /* DELETER_HH_ */
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sharded.hh"
|
||||
|
||||
template <typename Service>
|
||||
using distributed = seastar::sharded<Service>;
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "apply.hh"
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
/// \addtogroup future-util
|
||||
/// @{
|
||||
|
||||
/// do_with() holds an object alive for the duration until a future
|
||||
/// completes, and allow the code involved in making the future
|
||||
/// complete to have easy access to this object.
|
||||
///
|
||||
/// do_with() takes two arguments: The first is an temporary object (rvalue),
|
||||
/// the second is a function returning a future (a so-called "promise").
|
||||
/// The function is given (a moved copy of) this temporary object, by
|
||||
/// reference, and it is ensured that the object will not be destructed until
|
||||
/// the completion of the future returned by the function.
|
||||
///
|
||||
/// do_with() returns a future which resolves to whatever value the given future
|
||||
/// (returned by the given function) resolves to. This returned value must not
|
||||
/// contain references to the temporary object, as at that point the temporary
|
||||
/// is destructed.
|
||||
///
|
||||
/// \param rvalue a temporary value to protect while \c f is running
|
||||
/// \param f a callable, accepting an lvalue reference of the same type
|
||||
/// as \c rvalue, that will be accessible while \c f runs
|
||||
/// \return whatever \c f returns
|
||||
template<typename T, typename F>
|
||||
inline
|
||||
auto do_with(T&& rvalue, F&& f) {
|
||||
auto obj = std::make_unique<T>(std::forward<T>(rvalue));
|
||||
auto fut = f(*obj);
|
||||
return fut.finally([obj = std::move(obj)] () {});
|
||||
}
|
||||
|
||||
/// \cond internal
|
||||
template <typename Tuple, size_t... Idx>
|
||||
inline
|
||||
auto
|
||||
cherry_pick_tuple(std::index_sequence<Idx...>, Tuple&& tuple) {
|
||||
return std::make_tuple(std::get<Idx>(std::forward<Tuple>(tuple))...);
|
||||
}
|
||||
/// \endcond
|
||||
|
||||
/// Multiple argument variant of \ref do_with(T&& rvalue, F&& f).
|
||||
///
|
||||
/// This is the same as \ref do_with(T&& tvalue, F&& f), but accepts
|
||||
/// two or more rvalue parameters, which are held in memory while
|
||||
/// \c f executes. \c f will be called with all arguments as
|
||||
/// reference parameters.
|
||||
template <typename T1, typename T2, typename T3_or_F, typename... More>
|
||||
inline
|
||||
auto
|
||||
do_with(T1&& rv1, T2&& rv2, T3_or_F&& rv3, More&&... more) {
|
||||
auto all = std::forward_as_tuple(
|
||||
std::forward<T1>(rv1),
|
||||
std::forward<T2>(rv2),
|
||||
std::forward<T3_or_F>(rv3),
|
||||
std::forward<More>(more)...);
|
||||
constexpr size_t nr = std::tuple_size<decltype(all)>::value - 1;
|
||||
using idx = std::make_index_sequence<nr>;
|
||||
auto&& just_values = cherry_pick_tuple(idx(), std::move(all));
|
||||
auto&& just_func = std::move(std::get<nr>(std::move(all)));
|
||||
auto obj = std::make_unique<std::remove_reference_t<decltype(just_values)>>(std::move(just_values));
|
||||
auto fut = apply(just_func, *obj);
|
||||
return fut.finally([obj = std::move(obj)] () {});
|
||||
}
|
||||
|
||||
/// @}
|
||||
121
core/dpdk_rte.cc
121
core/dpdk_rte.cc
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
#ifdef HAVE_DPDK
|
||||
|
||||
#include "net/dpdk.hh"
|
||||
#include "core/dpdk_rte.hh"
|
||||
#include "util/conversions.hh"
|
||||
#include <experimental/optional>
|
||||
#include <rte_pci.h>
|
||||
|
||||
namespace dpdk {
|
||||
|
||||
bool eal::initialized = false;
|
||||
|
||||
void eal::init(cpuset cpus, boost::program_options::variables_map opts)
|
||||
{
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::stringstream mask;
|
||||
mask << std::hex << cpus.to_ulong();
|
||||
|
||||
// TODO: Inherit these from the app parameters - "opts"
|
||||
std::vector<std::vector<char>> args {
|
||||
string2vector("dpdk_args"),
|
||||
string2vector("-c"), string2vector(mask.str()),
|
||||
string2vector("-n"), string2vector("1")
|
||||
};
|
||||
|
||||
std::experimental::optional<std::string> hugepages_path;
|
||||
if (opts.count("hugepages")) {
|
||||
hugepages_path = opts["hugepages"].as<std::string>();
|
||||
}
|
||||
|
||||
// If "hugepages" is not provided and DPDK PMD drivers mode is requested -
|
||||
// use the default DPDK huge tables configuration.
|
||||
if (hugepages_path) {
|
||||
args.push_back(string2vector("--huge-dir"));
|
||||
args.push_back(string2vector(hugepages_path.value()));
|
||||
|
||||
//
|
||||
// We don't know what is going to be our networking configuration so we
|
||||
// assume there is going to be a queue per-CPU. Plus we'll give a DPDK
|
||||
// 64MB for "other stuff".
|
||||
//
|
||||
size_t size_MB = mem_size(cpus.count()) >> 20;
|
||||
std::stringstream size_MB_str;
|
||||
size_MB_str << size_MB;
|
||||
|
||||
args.push_back(string2vector("-m"));
|
||||
args.push_back(string2vector(size_MB_str.str()));
|
||||
} else if (!opts.count("dpdk-pmd")) {
|
||||
args.push_back(string2vector("--no-huge"));
|
||||
}
|
||||
#ifdef HAVE_OSV
|
||||
args.push_back(string2vector("--no-shconf"));
|
||||
#endif
|
||||
|
||||
std::vector<char*> cargs;
|
||||
|
||||
for (auto&& a: args) {
|
||||
cargs.push_back(a.data());
|
||||
}
|
||||
/* initialise the EAL for all */
|
||||
int ret = rte_eal_init(cargs.size(), cargs.data());
|
||||
if (ret < 0) {
|
||||
rte_exit(EXIT_FAILURE, "Cannot init EAL\n");
|
||||
}
|
||||
|
||||
// For 1.8 rte_eal_pci_probe() has been moved into the rte_eal_init().
|
||||
#ifdef RTE_VERSION_1_7
|
||||
/* probe the PCI devices in case we need to use DPDK drivers */
|
||||
if (rte_eal_pci_probe() < 0) {
|
||||
rte_exit(EXIT_FAILURE, "Cannot probe PCI\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
uint32_t __attribute__((weak)) qp_mempool_obj_size(bool hugetlbfs_membackend)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t eal::mem_size(int num_cpus, bool hugetlbfs_membackend)
|
||||
{
|
||||
size_t memsize = 0;
|
||||
//
|
||||
// PMD mempool memory:
|
||||
//
|
||||
// We don't know what is going to be our networking configuration so we
|
||||
// assume there is going to be a queue per-CPU.
|
||||
//
|
||||
memsize += num_cpus * qp_mempool_obj_size(hugetlbfs_membackend);
|
||||
|
||||
// Plus we'll give a DPDK 64MB for "other stuff".
|
||||
memsize += (64UL << 20);
|
||||
|
||||
return memsize;
|
||||
}
|
||||
|
||||
} // namespace dpdk
|
||||
|
||||
#endif // HAVE_DPDK
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
#ifndef DPDK_RTE_HH_
|
||||
#define DPDK_RTE_HH_
|
||||
|
||||
#ifdef HAVE_DPDK
|
||||
|
||||
#include <bitset>
|
||||
#include <rte_config.h>
|
||||
#include <rte_ethdev.h>
|
||||
#include <rte_version.h>
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
/*********************** Compat section ***************************************/
|
||||
// We currently support only versions 1.7 and above.
|
||||
// So, since currently the only above version is 1.8.x we will use it as "else"
|
||||
// of ver. 1.7.x.
|
||||
#if (RTE_VERSION >= RTE_VERSION_NUM(1,7,0,0)) && \
|
||||
(RTE_VERSION < RTE_VERSION_NUM(1,8,0,0))
|
||||
#define RTE_VERSION_1_7
|
||||
#endif
|
||||
|
||||
#ifdef RTE_VERSION_1_7
|
||||
#if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_MBUF_SCATTER_GATHER) || \
|
||||
defined(RTE_LIBRTE_IP_FRAG)
|
||||
#error "RTE_LIBRTE_PMD_BOND, RTE_MBUF_SCATTER_GATHER," \
|
||||
"and RTE_LIBRTE_IP_FRAG should be disabled in DPDK's " \
|
||||
"config/common_linuxapp"
|
||||
#endif
|
||||
|
||||
#define rte_mbuf_vlan_tci(m) ((m)->pkt.vlan_macip.f.vlan_tci)
|
||||
#define rte_mbuf_rss_hash(m) ((m)->pkt.hash.rss)
|
||||
#define rte_mbuf_data_len(m) ((m)->pkt.data_len)
|
||||
#define rte_mbuf_pkt_len(m) ((m)->pkt.pkt_len)
|
||||
#define rte_mbuf_next(m) ((m)->pkt.next)
|
||||
#define rte_mbuf_nb_segs(m) ((m)->pkt.nb_segs)
|
||||
#define rte_mbuf_l2_len(m) ((m)->pkt.vlan_macip.f.l2_len)
|
||||
#define rte_mbuf_l3_len(m) ((m)->pkt.vlan_macip.f.l3_len)
|
||||
#define rte_mbuf_buf_addr(m) ((m)->buf_addr)
|
||||
#define rte_mbuf_buf_physaddr(m) ((m)->buf_physaddr)
|
||||
#define rte_mbuf_buf_len(m) ((m)->buf_len)
|
||||
#else
|
||||
#if (RTE_VERSION < RTE_VERSION_NUM(2,0,0,0))
|
||||
#if defined(RTE_MBUF_REFCNT)
|
||||
#error "RTE_MBUF_REFCNT should be disabled in DPDK's config/common_linuxapp"
|
||||
#endif
|
||||
#endif
|
||||
#define rte_mbuf_vlan_tci(m) ((m)->vlan_tci)
|
||||
#define rte_mbuf_rss_hash(m) ((m)->hash.rss)
|
||||
#define rte_mbuf_data_len(m) ((m)->data_len)
|
||||
#define rte_mbuf_pkt_len(m) ((m)->pkt_len)
|
||||
#define rte_mbuf_next(m) ((m)->next)
|
||||
#define rte_mbuf_nb_segs(m) ((m)->nb_segs)
|
||||
#define rte_mbuf_l2_len(m) ((m)->l2_len)
|
||||
#define rte_mbuf_l3_len(m) ((m)->l3_len)
|
||||
#define rte_mbuf_buf_addr(m) ((m)->buf_addr)
|
||||
#define rte_mbuf_buf_physaddr(m) ((m)->buf_physaddr)
|
||||
#define rte_mbuf_buf_len(m) ((m)->buf_len)
|
||||
|
||||
#endif
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
namespace dpdk {
|
||||
|
||||
// DPDK Environment Abstraction Layer
|
||||
class eal {
|
||||
public:
|
||||
using cpuset = std::bitset<RTE_MAX_LCORE>;
|
||||
|
||||
static void init(cpuset cpus, boost::program_options::variables_map opts);
|
||||
/**
|
||||
* Returns the amount of memory needed for DPDK
|
||||
* @param num_cpus Number of CPUs the application is going to use
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static size_t mem_size(int num_cpus, bool hugetlbfs_membackend = true);
|
||||
static bool initialized;
|
||||
};
|
||||
|
||||
} // namespace dpdk
|
||||
#endif // HAVE_DPDK
|
||||
#endif // DPDK_RTE_HH_
|
||||
42
core/enum.hh
42
core/enum.hh
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* This header file defines a hash function for enum types, using the
|
||||
* standard hash function of the underlying type (such as int). This makes
|
||||
* it possible to inherit from this type to
|
||||
*/
|
||||
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <cstddef>
|
||||
|
||||
template <typename T>
|
||||
class enum_hash {
|
||||
static_assert(std::is_enum<T>::value, "must be an enum");
|
||||
public:
|
||||
std::size_t operator()(const T& e) const {
|
||||
using utype = typename std::underlying_type<T>::type;
|
||||
return std::hash<utype>()(static_cast<utype>(e));
|
||||
}
|
||||
};
|
||||
555
core/file.hh
555
core/file.hh
@@ -1,555 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef FILE_HH_
|
||||
#define FILE_HH_
|
||||
|
||||
#include "stream.hh"
|
||||
#include "sstring.hh"
|
||||
#include "core/shared_ptr.hh"
|
||||
#include "core/align.hh"
|
||||
#include "core/future-util.hh"
|
||||
#include <experimental/optional>
|
||||
#include <system_error>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/fs.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/// \addtogroup fileio-module
|
||||
/// @{
|
||||
|
||||
/// Enumeration describing the type of a directory entry being listed.
|
||||
///
|
||||
/// \see file::list_directory()
|
||||
enum class directory_entry_type {
|
||||
block_device,
|
||||
char_device,
|
||||
directory,
|
||||
fifo,
|
||||
link,
|
||||
regular,
|
||||
socket,
|
||||
};
|
||||
|
||||
/// A directory entry being listed.
|
||||
struct directory_entry {
|
||||
/// Name of the file in a directory entry. Will never be "." or "..". Only the last component is included.
|
||||
sstring name;
|
||||
/// Type of the directory entry, if known.
|
||||
std::experimental::optional<directory_entry_type> type;
|
||||
};
|
||||
|
||||
/// \cond internal
|
||||
class file_impl {
|
||||
public:
|
||||
virtual ~file_impl() {}
|
||||
|
||||
virtual future<size_t> write_dma(uint64_t pos, const void* buffer, size_t len) = 0;
|
||||
virtual future<size_t> write_dma(uint64_t pos, std::vector<iovec> iov) = 0;
|
||||
virtual future<size_t> read_dma(uint64_t pos, void* buffer, size_t len) = 0;
|
||||
virtual future<size_t> read_dma(uint64_t pos, std::vector<iovec> iov) = 0;
|
||||
virtual future<> flush(void) = 0;
|
||||
virtual future<struct stat> stat(void) = 0;
|
||||
virtual future<> truncate(uint64_t length) = 0;
|
||||
virtual future<> discard(uint64_t offset, uint64_t length) = 0;
|
||||
virtual future<> allocate(uint64_t position, uint64_t length) = 0;
|
||||
virtual future<uint64_t> size(void) = 0;
|
||||
virtual future<> close() = 0;
|
||||
virtual subscription<directory_entry> list_directory(std::function<future<> (directory_entry de)> next) = 0;
|
||||
|
||||
friend class reactor;
|
||||
};
|
||||
|
||||
class posix_file_impl : public file_impl {
|
||||
public:
|
||||
int _fd;
|
||||
posix_file_impl(int fd) : _fd(fd) {}
|
||||
virtual ~posix_file_impl() override;
|
||||
future<size_t> write_dma(uint64_t pos, const void* buffer, size_t len);
|
||||
future<size_t> write_dma(uint64_t pos, std::vector<iovec> iov);
|
||||
future<size_t> read_dma(uint64_t pos, void* buffer, size_t len);
|
||||
future<size_t> read_dma(uint64_t pos, std::vector<iovec> iov);
|
||||
future<> flush(void);
|
||||
future<struct stat> stat(void);
|
||||
future<> truncate(uint64_t length);
|
||||
future<> discard(uint64_t offset, uint64_t length);
|
||||
virtual future<> allocate(uint64_t position, uint64_t length) override;
|
||||
future<size_t> size(void);
|
||||
virtual future<> close() override;
|
||||
virtual subscription<directory_entry> list_directory(std::function<future<> (directory_entry de)> next) override;
|
||||
};
|
||||
|
||||
class blockdev_file_impl : public posix_file_impl {
|
||||
public:
|
||||
blockdev_file_impl(int fd) : posix_file_impl(fd) {}
|
||||
future<> truncate(uint64_t length) override;
|
||||
future<> discard(uint64_t offset, uint64_t length) override;
|
||||
future<size_t> size(void) override;
|
||||
virtual future<> allocate(uint64_t position, uint64_t length) override;
|
||||
};
|
||||
|
||||
inline
|
||||
shared_ptr<file_impl>
|
||||
make_file_impl(int fd) {
|
||||
auto r = ::ioctl(fd, BLKGETSIZE);
|
||||
if (r != -1) {
|
||||
return make_shared<blockdev_file_impl>(fd);
|
||||
} else {
|
||||
return make_shared<posix_file_impl>(fd);
|
||||
}
|
||||
}
|
||||
|
||||
/// \endcond
|
||||
|
||||
/// A data file on persistent storage.
|
||||
///
|
||||
/// File objects represent uncached, unbuffered files. As such great care
|
||||
/// must be taken to cache data at the application layer; neither seastar
|
||||
/// nor the OS will cache these file.
|
||||
///
|
||||
/// Data is transferred using direct memory access (DMA). This imposes
|
||||
/// restrictions on file offsets and data pointers. The former must be aligned
|
||||
/// on a 4096 byte boundary, while a 512 byte boundary suffices for the latter.
|
||||
class file {
|
||||
shared_ptr<file_impl> _file_impl;
|
||||
private:
|
||||
explicit file(int fd) : _file_impl(make_file_impl(fd)) {}
|
||||
|
||||
public:
|
||||
/// Copies a file object. The new and old objects refer to the
|
||||
/// same underlying file.
|
||||
///
|
||||
/// \param x file object to be copied
|
||||
file(const file& x) = default;
|
||||
/// Moves a file object.
|
||||
file(file&& x) : _file_impl(std::move(x._file_impl)) {}
|
||||
/// Assigns a file object. After assignent, the destination and source refer
|
||||
/// to the same underlying file.
|
||||
///
|
||||
/// \param x file object to assign to `this`.
|
||||
file& operator=(const file& x) noexcept = default;
|
||||
/// Moves assigns a file object.
|
||||
file& operator=(file&& x) noexcept = default;
|
||||
|
||||
// O_DIRECT reading requires that buffer, offset, and read length, are
|
||||
// all aligned. Alignment of 4096 was necessary in the past, but no longer
|
||||
// is - 512 is usually enough; But we'll need to use BLKSSZGET ioctl to
|
||||
// be sure it is really enough on this filesystem. 4096 is always safe.
|
||||
// In addition, if we start reading in things outside page boundaries,
|
||||
// we will end up with various pages around, some of them with
|
||||
// overlapping ranges. Those would be very challenging to cache.
|
||||
|
||||
/// Alignment requirement for file offsets
|
||||
static constexpr uint64_t dma_alignment = 4096;
|
||||
|
||||
// Make sure alignment is a power of 2
|
||||
static_assert(dma_alignment > 0 && !(dma_alignment & (dma_alignment - 1)),
|
||||
"dma_alignment must be a power of 2");
|
||||
|
||||
|
||||
/**
|
||||
* Perform a single DMA read operation.
|
||||
*
|
||||
* @param aligned_pos offset to begin reading at (should be aligned)
|
||||
* @param aligned_buffer output buffer (should be aligned)
|
||||
* @param aligned_len number of bytes to read (should be aligned)
|
||||
*
|
||||
* Alignment is HW dependent but use 4KB alignment to be on the safe side as
|
||||
* explained above.
|
||||
*
|
||||
* @return number of bytes actually read
|
||||
* @throw exception in case of I/O error
|
||||
*/
|
||||
template <typename CharType>
|
||||
future<size_t>
|
||||
dma_read(uint64_t aligned_pos, CharType* aligned_buffer, size_t aligned_len) {
|
||||
return _file_impl->read_dma(aligned_pos, aligned_buffer, aligned_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the requested amount of bytes starting from the given offset.
|
||||
*
|
||||
* @param pos offset to begin reading from
|
||||
* @param len number of bytes to read
|
||||
*
|
||||
* @return temporary buffer containing the requested data.
|
||||
* @throw exception in case of I/O error
|
||||
*
|
||||
* This function doesn't require any alignment for both "pos" and "len"
|
||||
*
|
||||
* @note size of the returned buffer may be smaller than "len" if EOF is
|
||||
* reached of in case of I/O error.
|
||||
*/
|
||||
template <typename CharType>
|
||||
future<temporary_buffer<CharType>> dma_read(uint64_t pos, size_t len) {
|
||||
return dma_read_bulk<CharType>(pos, len).then(
|
||||
[len] (temporary_buffer<CharType> buf) {
|
||||
if (len < buf.size()) {
|
||||
buf.trim(len);
|
||||
}
|
||||
|
||||
return std::move(buf);
|
||||
});
|
||||
}
|
||||
|
||||
/// Error thrown when attempting to read past end-of-file
|
||||
/// with \ref dma_read_exactly().
|
||||
class eof_error : public std::exception {};
|
||||
|
||||
/**
|
||||
* Read the exact amount of bytes.
|
||||
*
|
||||
* @param pos offset in a file to begin reading from
|
||||
* @param len number of bytes to read
|
||||
*
|
||||
* @return temporary buffer containing the read data
|
||||
* @throw end_of_file_error if EOF is reached, file_io_error or
|
||||
* std::system_error in case of I/O error.
|
||||
*/
|
||||
template <typename CharType>
|
||||
future<temporary_buffer<CharType>>
|
||||
dma_read_exactly(uint64_t pos, size_t len) {
|
||||
return dma_read<CharType>(pos, len).then(
|
||||
[pos, len] (auto buf) {
|
||||
if (buf.size() < len) {
|
||||
throw eof_error();
|
||||
}
|
||||
|
||||
return std::move(buf);
|
||||
});
|
||||
}
|
||||
|
||||
/// Performs a DMA read into the specified iovec.
|
||||
///
|
||||
/// \param pos offset to read from. Must be aligned to \ref dma_alignment.
|
||||
/// \param iov vector of address/size pairs to read into. Addresses must be
|
||||
/// aligned.
|
||||
/// \return a future representing the number of bytes actually read. A short
|
||||
/// read may happen due to end-of-file or an I/O error.
|
||||
future<size_t> dma_read(uint64_t pos, std::vector<iovec> iov) {
|
||||
return _file_impl->read_dma(pos, std::move(iov));
|
||||
}
|
||||
|
||||
/// Performs a DMA write from the specified buffer.
|
||||
///
|
||||
/// \param pos offset to write into. Must be aligned to \ref dma_alignment.
|
||||
/// \param buffer aligned address of buffer to read from. Buffer must exists
|
||||
/// until the future is made ready.
|
||||
/// \param len number of bytes to write. Must be aligned.
|
||||
/// \return a future representing the number of bytes actually written. A short
|
||||
/// write may happen due to an I/O error.
|
||||
template <typename CharType>
|
||||
future<size_t> dma_write(uint64_t pos, const CharType* buffer, size_t len) {
|
||||
return _file_impl->write_dma(pos, buffer, len);
|
||||
}
|
||||
|
||||
/// Performs a DMA write to the specified iovec.
|
||||
///
|
||||
/// \param pos offset to write into. Must be aligned to \ref dma_alignment.
|
||||
/// \param iov vector of address/size pairs to write from. Addresses must be
|
||||
/// aligned.
|
||||
/// \return a future representing the number of bytes actually written. A short
|
||||
/// write may happen due to an I/O error.
|
||||
future<size_t> dma_write(uint64_t pos, std::vector<iovec> iov) {
|
||||
return _file_impl->write_dma(pos, std::move(iov));
|
||||
}
|
||||
|
||||
/// Causes any previously written data to be made stable on persistent storage.
|
||||
///
|
||||
/// Prior to a flush, written data may or may not survive a power failure. After
|
||||
/// a flush, data is guaranteed to be on disk.
|
||||
future<> flush() {
|
||||
return _file_impl->flush();
|
||||
}
|
||||
|
||||
/// Returns \c stat information about the file.
|
||||
future<struct stat> stat() {
|
||||
return _file_impl->stat();
|
||||
}
|
||||
|
||||
/// Truncates the file to a specified length.
|
||||
future<> truncate(uint64_t length) {
|
||||
return _file_impl->truncate(length);
|
||||
}
|
||||
|
||||
/// Preallocate disk blocks for a specified byte range.
|
||||
///
|
||||
/// Requests the file system to allocate disk blocks to
|
||||
/// back the specified range (\c length bytes starting at
|
||||
/// \c position). The range may be outside the current file
|
||||
/// size; the blocks can then be used when appending to the
|
||||
/// file.
|
||||
///
|
||||
/// \param position beginning of the range at which to allocate
|
||||
/// blocks.
|
||||
/// \parm length length of range to allocate.
|
||||
/// \return future that becomes ready when the operation completes.
|
||||
future<> allocate(uint64_t position, uint64_t length) {
|
||||
return _file_impl->allocate(position, length);
|
||||
}
|
||||
|
||||
/// Discard unneeded data from the file.
|
||||
///
|
||||
/// The discard operation tells the file system that a range of offsets
|
||||
/// (which be aligned) is no longer needed and can be reused.
|
||||
future<> discard(uint64_t offset, uint64_t length) {
|
||||
return _file_impl->discard(offset, length);
|
||||
}
|
||||
|
||||
/// Gets the file size.
|
||||
future<uint64_t> size() {
|
||||
return _file_impl->size();
|
||||
}
|
||||
|
||||
/// Closes the file.
|
||||
///
|
||||
/// Flushes any pending operations and release any resources associated with
|
||||
/// the file (except for stable storage).
|
||||
///
|
||||
/// \note
|
||||
/// to ensure file data reaches stable storage, you must call \ref flush()
|
||||
/// before calling \c close().
|
||||
future<> close() {
|
||||
return _file_impl->close();
|
||||
}
|
||||
|
||||
/// Returns a directory listing, given that this file object is a directory.
|
||||
subscription<directory_entry> list_directory(std::function<future<> (directory_entry de)> next) {
|
||||
return _file_impl->list_directory(std::move(next));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a data bulk containing the provided addresses range that starts at
|
||||
* the given offset and ends at either the address aligned to
|
||||
* dma_alignment (4KB) or at the file end.
|
||||
*
|
||||
* @param offset starting address of the range the read bulk should contain
|
||||
* @param range_size size of the addresses range
|
||||
*
|
||||
* @return temporary buffer containing the read data bulk.
|
||||
* @throw system_error exception in case of I/O error or eof_error when
|
||||
* "offset" is beyond EOF.
|
||||
*/
|
||||
template <typename CharType>
|
||||
future<temporary_buffer<CharType>>
|
||||
dma_read_bulk(uint64_t offset, size_t range_size);
|
||||
|
||||
private:
|
||||
template <typename CharType>
|
||||
struct read_state;
|
||||
|
||||
/**
|
||||
* Try to read from the given position where the previous short read has
|
||||
* stopped. Check the EOF condition.
|
||||
*
|
||||
* The below code assumes the following: short reads due to I/O errors
|
||||
* always end at address aligned to HW block boundary. Therefore if we issue
|
||||
* a new read operation from the next position we are promised to get an
|
||||
* error (different from EINVAL). If we've got a short read because we have
|
||||
* reached EOF then the above read would either return a zero-length success
|
||||
* (if the file size is aligned to HW block size) or an EINVAL error (if
|
||||
* file length is not aligned to HW block size).
|
||||
*
|
||||
* @param pos offset to read from
|
||||
* @param len number of bytes to read
|
||||
*
|
||||
* @return temporary buffer with read data or zero-sized temporary buffer if
|
||||
* pos is at or beyond EOF.
|
||||
* @throw appropriate exception in case of I/O error.
|
||||
*/
|
||||
template <typename CharType>
|
||||
future<temporary_buffer<CharType>>
|
||||
read_maybe_eof(uint64_t pos, size_t len);
|
||||
|
||||
friend class reactor;
|
||||
};
|
||||
|
||||
/// \cond internal
|
||||
|
||||
template <typename CharType>
|
||||
struct file::read_state {
|
||||
typedef temporary_buffer<CharType> tmp_buf_type;
|
||||
|
||||
read_state(uint64_t offset, uint64_t front, size_t to_read)
|
||||
: buf(tmp_buf_type::aligned(file::dma_alignment,
|
||||
align_up(to_read, file::dma_alignment)))
|
||||
, _offset(offset)
|
||||
, _to_read(to_read)
|
||||
, _front(front) {}
|
||||
|
||||
bool done() const {
|
||||
return eof || pos >= _to_read;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim the buffer to the actual number of read bytes and cut the
|
||||
* bytes from offset 0 till "_front".
|
||||
*
|
||||
* @note this function has to be called only if we read bytes beyond
|
||||
* "_front".
|
||||
*/
|
||||
void trim_buf_before_ret() {
|
||||
if (have_good_bytes()) {
|
||||
buf.trim(pos);
|
||||
buf.trim_front(_front);
|
||||
} else {
|
||||
buf.trim(0);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t cur_offset() const {
|
||||
return _offset + pos;
|
||||
}
|
||||
|
||||
size_t left_space() const {
|
||||
return buf.size() - pos;
|
||||
}
|
||||
|
||||
size_t left_to_read() const {
|
||||
// positive as long as (done() == false)
|
||||
return _to_read - pos;
|
||||
}
|
||||
|
||||
void append_new_data(tmp_buf_type& new_data) {
|
||||
auto to_copy = std::min(left_space(), new_data.size());
|
||||
|
||||
std::memcpy(buf.get_write() + pos, new_data.get(), to_copy);
|
||||
pos += to_copy;
|
||||
}
|
||||
|
||||
bool have_good_bytes() const {
|
||||
return pos > _front;
|
||||
}
|
||||
|
||||
public:
|
||||
bool eof = false;
|
||||
tmp_buf_type buf;
|
||||
size_t pos = 0;
|
||||
private:
|
||||
uint64_t _offset;
|
||||
size_t _to_read;
|
||||
uint64_t _front;
|
||||
};
|
||||
|
||||
template <typename CharType>
|
||||
future<temporary_buffer<CharType>>
|
||||
file::dma_read_bulk(uint64_t offset, size_t range_size) {
|
||||
using tmp_buf_type = typename read_state<CharType>::tmp_buf_type;
|
||||
|
||||
auto front = offset & (dma_alignment - 1);
|
||||
offset -= front;
|
||||
range_size += front;
|
||||
|
||||
auto rstate = make_lw_shared<read_state<CharType>>(offset, front,
|
||||
range_size);
|
||||
|
||||
//
|
||||
// First, try to read directly into the buffer. Most of the reads will
|
||||
// end here.
|
||||
//
|
||||
auto read = dma_read(offset, rstate->buf.get_write(),
|
||||
rstate->buf.size());
|
||||
|
||||
return read.then([rstate, this] (size_t size) mutable {
|
||||
rstate->pos = size;
|
||||
|
||||
//
|
||||
// If we haven't read all required data at once -
|
||||
// start read-copy sequence. We can't continue with direct reads
|
||||
// into the previously allocated buffer here since we have to ensure
|
||||
// the aligned read length and thus the aligned destination buffer
|
||||
// size.
|
||||
//
|
||||
// The copying will actually take place only if there was a HW glitch.
|
||||
// In EOF case or in case of a persistent I/O error the only overhead is
|
||||
// an extra allocation.
|
||||
//
|
||||
return do_until(
|
||||
[rstate] { return rstate->done(); },
|
||||
[rstate, this] () mutable {
|
||||
return read_maybe_eof<CharType>(
|
||||
rstate->cur_offset(), rstate->left_to_read()).then(
|
||||
[rstate] (auto buf1) mutable {
|
||||
if (buf1.size()) {
|
||||
rstate->append_new_data(buf1);
|
||||
} else {
|
||||
rstate->eof = true;
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
});
|
||||
}).then([rstate] () mutable {
|
||||
//
|
||||
// If we are here we are promised to have read some bytes beyond
|
||||
// "front" so we may trim straight away.
|
||||
//
|
||||
rstate->trim_buf_before_ret();
|
||||
return make_ready_future<tmp_buf_type>(std::move(rstate->buf));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename CharType>
|
||||
future<temporary_buffer<CharType>>
|
||||
file::read_maybe_eof(uint64_t pos, size_t len) {
|
||||
//
|
||||
// We have to allocate a new aligned buffer to make sure we don't get
|
||||
// an EINVAL error due to unaligned destination buffer.
|
||||
//
|
||||
temporary_buffer<CharType> buf = temporary_buffer<CharType>::aligned(
|
||||
dma_alignment, align_up(len, dma_alignment));
|
||||
|
||||
// try to read a single bulk from the given position
|
||||
return dma_read(pos, buf.get_write(), buf.size()).then_wrapped(
|
||||
[buf = std::move(buf)](future<size_t> f) mutable {
|
||||
try {
|
||||
size_t size = std::get<0>(f.get());
|
||||
|
||||
buf.trim(size);
|
||||
|
||||
return std::move(buf);
|
||||
} catch (std::system_error& e) {
|
||||
//
|
||||
// TODO: implement a non-trowing file_impl::dma_read() interface to
|
||||
// avoid the exceptions throwing in a good flow completely.
|
||||
// Otherwise for users that don't want to care about the
|
||||
// underlying file size and preventing the attempts to read
|
||||
// bytes beyond EOF there will always be at least one
|
||||
// exception throwing at the file end for files with unaligned
|
||||
// length.
|
||||
//
|
||||
if (e.code().value() == EINVAL) {
|
||||
buf.trim(0);
|
||||
return std::move(buf);
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// \endcond
|
||||
|
||||
/// @}
|
||||
|
||||
#endif /* FILE_HH_ */
|
||||
128
core/fstream.cc
128
core/fstream.cc
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#include "fstream.hh"
|
||||
#include "align.hh"
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
|
||||
class file_data_source_impl : public data_source_impl {
|
||||
lw_shared_ptr<file> _file;
|
||||
uint64_t _pos;
|
||||
size_t _buffer_size;
|
||||
public:
|
||||
file_data_source_impl(lw_shared_ptr<file> f, uint64_t pos, size_t buffer_size)
|
||||
: _file(std::move(f)), _pos(pos), _buffer_size(buffer_size) {}
|
||||
virtual future<temporary_buffer<char>> get() override {
|
||||
using buf_type = temporary_buffer<char>;
|
||||
return _file->dma_read_bulk<char>(_pos, _buffer_size).then(
|
||||
[this] (buf_type buf) {
|
||||
_pos += buf.size();
|
||||
return std::move(buf);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class file_data_source : public data_source {
|
||||
public:
|
||||
file_data_source(lw_shared_ptr<file> f, uint64_t offset, size_t buffer_size)
|
||||
: data_source(std::make_unique<file_data_source_impl>(
|
||||
std::move(f), offset, buffer_size)) {}
|
||||
};
|
||||
|
||||
input_stream<char> make_file_input_stream(
|
||||
lw_shared_ptr<file> f, uint64_t offset, size_t buffer_size) {
|
||||
return input_stream<char>(file_data_source(std::move(f), offset, buffer_size));
|
||||
}
|
||||
|
||||
class file_data_sink_impl : public data_sink_impl {
|
||||
lw_shared_ptr<file> _file;
|
||||
file_output_stream_options _options;
|
||||
uint64_t _pos = 0;
|
||||
uint64_t _last_preallocation = 0;
|
||||
public:
|
||||
file_data_sink_impl(lw_shared_ptr<file> f, file_output_stream_options options)
|
||||
: _file(std::move(f)), _options(options) {}
|
||||
future<> put(net::packet data) { abort(); }
|
||||
virtual temporary_buffer<char> allocate_buffer(size_t size) override {
|
||||
return temporary_buffer<char>::aligned(file::dma_alignment, size);
|
||||
}
|
||||
virtual future<> put(temporary_buffer<char> buf) override {
|
||||
// put() must usually be of chunks multiple of file::dma_alignment.
|
||||
// Only the last part can have an unaligned length. If put() was
|
||||
// called again with an unaligned pos, we have a bug in the caller.
|
||||
assert(!(_pos & (file::dma_alignment - 1)));
|
||||
bool truncate = false;
|
||||
auto pos = _pos;
|
||||
_pos += buf.size();
|
||||
auto p = static_cast<const char*>(buf.get());
|
||||
size_t buf_size = buf.size();
|
||||
|
||||
if ((buf.size() & (file::dma_alignment - 1)) != 0) {
|
||||
// If buf size isn't aligned, copy its content into a new aligned buf.
|
||||
// This should only happen when the user calls output_stream::flush().
|
||||
auto tmp = allocate_buffer(align_up(buf.size(), file::dma_alignment));
|
||||
::memcpy(tmp.get_write(), buf.get(), buf.size());
|
||||
buf = std::move(tmp);
|
||||
p = buf.get();
|
||||
buf_size = buf.size();
|
||||
truncate = true;
|
||||
}
|
||||
auto prealloc = make_ready_future<>();
|
||||
if (pos + buf_size > _last_preallocation) {
|
||||
auto old = _last_preallocation;
|
||||
_last_preallocation = align_down<uint64_t>(old + _options.preallocation_size, _options.preallocation_size);
|
||||
prealloc = _file->allocate(old, _last_preallocation - old);
|
||||
}
|
||||
return prealloc.then([this, pos, p, buf_size, truncate, buf = std::move(buf)] () mutable {
|
||||
return _file->dma_write(pos, p, buf_size).then(
|
||||
[this, buf = std::move(buf), truncate] (size_t size) {
|
||||
if (truncate) {
|
||||
return _file->truncate(_pos);
|
||||
}
|
||||
return make_ready_future<>();
|
||||
});
|
||||
});
|
||||
}
|
||||
future<> close() {
|
||||
return _file->flush().then([this] {
|
||||
return _file->close();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class file_data_sink : public data_sink {
|
||||
public:
|
||||
file_data_sink(lw_shared_ptr<file> f, file_output_stream_options options)
|
||||
: data_sink(std::make_unique<file_data_sink_impl>(
|
||||
std::move(f), options)) {}
|
||||
};
|
||||
|
||||
output_stream<char> make_file_output_stream(lw_shared_ptr<file> f, size_t buffer_size) {
|
||||
file_output_stream_options options;
|
||||
options.buffer_size = buffer_size;
|
||||
return make_file_output_stream(std::move(f), options);
|
||||
}
|
||||
|
||||
output_stream<char> make_file_output_stream(lw_shared_ptr<file> f, file_output_stream_options options) {
|
||||
return output_stream<char>(file_data_sink(std::move(f), options), options.buffer_size, true);
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// File <-> streams adapters
|
||||
//
|
||||
// Seastar files are block-based due to the reliance on DMA - you must read
|
||||
// on sector boundaries. The adapters in this file provide a byte stream
|
||||
// interface to files, while retaining the zero-copy characteristics of
|
||||
// seastar files.
|
||||
|
||||
#include "file.hh"
|
||||
#include "iostream.hh"
|
||||
#include "shared_ptr.hh"
|
||||
|
||||
// Create an input_stream for reading starting at a given position of the
|
||||
// given file. Multiple fibers of execution (continuations) may safely open
|
||||
// multiple input streams concurrently for the same file.
|
||||
input_stream<char> make_file_input_stream(
|
||||
lw_shared_ptr<file> file, uint64_t offset = 0,
|
||||
uint64_t buffer_size = 8192);
|
||||
|
||||
struct file_output_stream_options {
|
||||
unsigned buffer_size = 8192;
|
||||
unsigned preallocation_size = 1024*1024; // 1MB
|
||||
};
|
||||
|
||||
// Create an output_stream for writing starting at the position zero of a
|
||||
// newly created file.
|
||||
// NOTE: flush() should be the last thing to be called on a file output stream.
|
||||
output_stream<char> make_file_output_stream(
|
||||
lw_shared_ptr<file> file,
|
||||
uint64_t buffer_size = 8192);
|
||||
|
||||
/// Create an output_stream for writing starting at the position zero of a
|
||||
/// newly created file.
|
||||
/// NOTE: flush() should be the last thing to be called on a file output stream.
|
||||
output_stream<char> make_file_output_stream(
|
||||
lw_shared_ptr<file> file,
|
||||
file_output_stream_options options);
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
|
||||
template<typename T>
|
||||
struct function_traits;
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct function_traits<Ret(Args...)>
|
||||
{
|
||||
using return_type = Ret;
|
||||
using args_as_tuple = std::tuple<Args...>;
|
||||
using signature = Ret (Args...);
|
||||
|
||||
static constexpr std::size_t arity = sizeof...(Args);
|
||||
|
||||
template <std::size_t N>
|
||||
struct arg
|
||||
{
|
||||
static_assert(N < arity, "no such parameter index.");
|
||||
using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
|
||||
};
|
||||
};
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct function_traits<Ret(*)(Args...)> : public function_traits<Ret(Args...)>
|
||||
{};
|
||||
|
||||
template <typename T, typename Ret, typename... Args>
|
||||
struct function_traits<Ret(T::*)(Args...)> : public function_traits<Ret(Args...)>
|
||||
{};
|
||||
|
||||
template <typename T, typename Ret, typename... Args>
|
||||
struct function_traits<Ret(T::*)(Args...) const> : public function_traits<Ret(Args...)>
|
||||
{};
|
||||
|
||||
template <typename T>
|
||||
struct function_traits : public function_traits<decltype(&T::operator())>
|
||||
{};
|
||||
|
||||
template<typename T>
|
||||
struct function_traits<T&> : public function_traits<std::remove_reference_t<T>>
|
||||
{};
|
||||
|
||||
@@ -1,543 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
|
||||
/** @file */
|
||||
|
||||
#ifndef CORE_FUTURE_UTIL_HH_
|
||||
#define CORE_FUTURE_UTIL_HH_
|
||||
|
||||
#include "task.hh"
|
||||
#include "future.hh"
|
||||
#include "shared_ptr.hh"
|
||||
#include <tuple>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
/// \cond internal
|
||||
extern __thread size_t task_quota;
|
||||
/// \endcond
|
||||
|
||||
|
||||
/// \addtogroup future-util
|
||||
/// @{
|
||||
|
||||
/// Run tasks in parallel (iterator version).
|
||||
///
|
||||
/// Given a range [\c begin, \c end) of objects, run \c func on each \c *i in
|
||||
/// the range, and return a future<> that resolves when all the functions
|
||||
/// complete. \c func should return a future<> that indicates when it is
|
||||
/// complete. All invocations are performed in parallel.
|
||||
///
|
||||
/// \param begin an \c InputIterator designating the beginning of the range
|
||||
/// \param end an \c InputIterator designating the end of the range
|
||||
/// \param func Function to apply to each element in the range (returning
|
||||
/// a \c future<>)
|
||||
/// \return a \c future<> that resolves when all the function invocations
|
||||
/// complete. If one or more return an exception, the return value
|
||||
/// contains one of the exceptions.
|
||||
template <typename Iterator, typename Func>
|
||||
inline
|
||||
future<>
|
||||
parallel_for_each(Iterator begin, Iterator end, Func&& func) {
|
||||
auto ret = make_ready_future<>();
|
||||
while (begin != end) {
|
||||
auto f = func(*begin++).then([ret = std::move(ret)] () mutable {
|
||||
return std::move(ret);
|
||||
});
|
||||
ret = std::move(f);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Run tasks in parallel (range version).
|
||||
///
|
||||
/// Given a \c range of objects, apply \c func to each object
|
||||
/// in the range, and return a future<> that resolves when all
|
||||
/// the functions complete. \c func should return a future<> that indicates
|
||||
/// when it is complete. All invocations are performed in parallel.
|
||||
///
|
||||
/// \param range A range of objects to iterate run \c func on
|
||||
/// \param func A callable, accepting reference to the range's
|
||||
/// \c value_type, and returning a \c future<>.
|
||||
/// \return a \c future<> that becomes ready when the entire range
|
||||
/// was processed. If one or more of the invocations of
|
||||
/// \c func returned an exceptional future, then the return
|
||||
/// value will contain one of those exceptions.
|
||||
template <typename Range, typename Func>
|
||||
inline
|
||||
future<>
|
||||
parallel_for_each(Range&& range, Func&& func) {
|
||||
return parallel_for_each(std::begin(range), std::end(range),
|
||||
std::forward<Func>(func));
|
||||
}
|
||||
|
||||
// The AsyncAction concept represents an action which can complete later than
|
||||
// the actual function invocation. It is represented by a function which
|
||||
// returns a future which resolves when the action is done.
|
||||
|
||||
/// \cond internal
|
||||
template<typename AsyncAction, typename StopCondition>
|
||||
static inline
|
||||
void do_until_continued(StopCondition&& stop_cond, AsyncAction&& action, promise<> p) {
|
||||
while (!stop_cond()) {
|
||||
try {
|
||||
auto&& f = action();
|
||||
if (!f.available()) {
|
||||
f.then_wrapped([action = std::forward<AsyncAction>(action),
|
||||
stop_cond = std::forward<StopCondition>(stop_cond), p = std::move(p)](std::result_of_t<AsyncAction()> fut) mutable {
|
||||
try {
|
||||
fut.get();
|
||||
do_until_continued(stop_cond, std::forward<AsyncAction>(action), std::move(p));
|
||||
} catch(...) {
|
||||
p.set_exception(std::current_exception());
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (f.failed()) {
|
||||
f.forward_to(std::move(p));
|
||||
return;
|
||||
}
|
||||
} catch (...) {
|
||||
p.set_exception(std::current_exception());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
p.set_value();
|
||||
}
|
||||
/// \endcond
|
||||
|
||||
enum class stop_iteration { no, yes };
|
||||
|
||||
/// Invokes given action until it fails or the function requests iteration to stop by returning
|
||||
/// \c stop_iteration::yes.
|
||||
///
|
||||
/// \param action a callable taking no arguments, returning a future<stop_iteration>. Will
|
||||
/// be called again as soon as the future resolves, unless the
|
||||
/// future fails, action throws, or it resolves with \c stop_iteration::yes.
|
||||
/// If \c action is an r-value it can be moved in the middle of iteration.
|
||||
/// \return a ready future if we stopped successfully, or a failed future if
|
||||
/// a call to to \c action failed.
|
||||
template<typename AsyncAction>
|
||||
static inline
|
||||
future<> repeat(AsyncAction&& action) {
|
||||
using futurator = futurize<std::result_of_t<AsyncAction()>>;
|
||||
static_assert(std::is_same<future<stop_iteration>, typename futurator::type>::value, "bad AsyncAction signature");
|
||||
|
||||
while (task_quota) {
|
||||
try {
|
||||
auto f = futurator::apply(action);
|
||||
|
||||
if (!f.available()) {
|
||||
return f.then([action = std::forward<AsyncAction>(action)] (stop_iteration stop) mutable {
|
||||
if (stop == stop_iteration::yes) {
|
||||
return make_ready_future<>();
|
||||
} else {
|
||||
return repeat(std::forward<AsyncAction>(action));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (f.get0() == stop_iteration::yes) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
} catch (...) {
|
||||
return make_exception_future<>(std::current_exception());
|
||||
}
|
||||
|
||||
--task_quota;
|
||||
}
|
||||
|
||||
promise<> p;
|
||||
auto f = p.get_future();
|
||||
schedule(make_task([action = std::forward<AsyncAction>(action), p = std::move(p)] () mutable {
|
||||
repeat(std::forward<AsyncAction>(action)).forward_to(std::move(p));
|
||||
}));
|
||||
return f;
|
||||
}
|
||||
|
||||
/// Invokes given action until it fails or given condition evaluates to true.
|
||||
///
|
||||
/// \param stop_cond a callable taking no arguments, returning a boolean that
|
||||
/// evalutes to true when you don't want to call \c action
|
||||
/// any longer
|
||||
/// \param action a callable taking no arguments, returning a future<>. Will
|
||||
/// be called again as soon as the future resolves, unless the
|
||||
/// future fails, or \c stop_cond returns \c true.
|
||||
/// \return a ready future if we stopped successfully, or a failed future if
|
||||
/// a call to to \c action failed.
|
||||
template<typename AsyncAction, typename StopCondition>
|
||||
static inline
|
||||
future<> do_until(StopCondition&& stop_cond, AsyncAction&& action) {
|
||||
promise<> p;
|
||||
auto f = p.get_future();
|
||||
do_until_continued(std::forward<StopCondition>(stop_cond),
|
||||
std::forward<AsyncAction>(action), std::move(p));
|
||||
return f;
|
||||
}
|
||||
|
||||
/// Invoke given action until it fails.
|
||||
///
|
||||
/// Calls \c action repeatedly until it returns a failed future.
|
||||
///
|
||||
/// \param action a callable taking no arguments, returning a \c future<>
|
||||
/// that becomes ready when you wish it to be called again.
|
||||
/// \return a future<> that will resolve to the first failure of \c action
|
||||
template<typename AsyncAction>
|
||||
static inline
|
||||
future<> keep_doing(AsyncAction&& action) {
|
||||
return repeat([action = std::forward<AsyncAction>(action)] () mutable {
|
||||
return action().then([] {
|
||||
return stop_iteration::no;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Call a function for each item in a range, sequentially (iterator version).
|
||||
///
|
||||
/// For each item in a range, call a function, waiting for the previous
|
||||
/// invocation to complete before calling the next one.
|
||||
///
|
||||
/// \param begin an \c InputIterator designating the beginning of the range
|
||||
/// \param end an \c InputIterator designating the endof the range
|
||||
/// \param action a callable, taking a reference to objects from the range
|
||||
/// as a parameter, and returning a \c future<> that resolves
|
||||
/// when it is acceptable to process the next item.
|
||||
/// \return a ready future on success, or the first failed future if
|
||||
/// \c action failed.
|
||||
template<typename Iterator, typename AsyncAction>
|
||||
static inline
|
||||
future<> do_for_each(Iterator begin, Iterator end, AsyncAction&& action) {
|
||||
if (begin == end) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
while (true) {
|
||||
auto f = action(*begin++);
|
||||
if (begin == end) {
|
||||
return f;
|
||||
}
|
||||
if (!f.available()) {
|
||||
return std::move(f).then([action = std::forward<AsyncAction>(action),
|
||||
begin = std::move(begin), end = std::move(end)] () mutable {
|
||||
return do_for_each(std::move(begin), std::move(end), std::forward<AsyncAction>(action));
|
||||
});
|
||||
}
|
||||
if (f.failed()) {
|
||||
return std::move(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Call a function for each item in a range, sequentially (range version).
|
||||
///
|
||||
/// For each item in a range, call a function, waiting for the previous
|
||||
/// invocation to complete before calling the next one.
|
||||
///
|
||||
/// \param range an \c Range object designating input values
|
||||
/// \param action a callable, taking a reference to objects from the range
|
||||
/// as a parameter, and returning a \c future<> that resolves
|
||||
/// when it is acceptable to process the next item.
|
||||
/// \return a ready future on success, or the first failed future if
|
||||
/// \c action failed.
|
||||
template<typename Container, typename AsyncAction>
|
||||
static inline
|
||||
future<> do_for_each(Container& c, AsyncAction&& action) {
|
||||
return do_for_each(std::begin(c), std::end(c), std::forward<AsyncAction>(action));
|
||||
}
|
||||
|
||||
/// \cond internal
|
||||
inline
|
||||
future<std::tuple<>>
|
||||
when_all() {
|
||||
return make_ready_future<std::tuple<>>();
|
||||
}
|
||||
|
||||
// gcc can't capture a parameter pack, so we need to capture
|
||||
// a tuple and use apply. But apply cannot accept an overloaded
|
||||
// function pointer as its first parameter, so provide this instead.
|
||||
struct do_when_all {
|
||||
template <typename... Future>
|
||||
future<std::tuple<Future...>> operator()(Future&&... fut) const {
|
||||
return when_all(std::move(fut)...);
|
||||
}
|
||||
};
|
||||
/// \endcond
|
||||
|
||||
/// Wait for many futures to complete, capturing possible errors (variadic version).
|
||||
///
|
||||
/// Given a variable number of futures as input, wait for all of them
|
||||
/// to resolve (either successfully or with an exception), and return
|
||||
/// them as a tuple so individual values or exceptions can be examined.
|
||||
///
|
||||
/// \param fut the first future to wait for
|
||||
/// \param rest more futures to wait for
|
||||
/// \return an \c std::tuple<> of all the futures in the input; when
|
||||
/// ready, all contained futures will be ready as well.
|
||||
template <typename... FutureArgs, typename... Rest>
|
||||
inline
|
||||
future<std::tuple<future<FutureArgs...>, Rest...>>
|
||||
when_all(future<FutureArgs...>&& fut, Rest&&... rest) {
|
||||
using Future = future<FutureArgs...>;
|
||||
return fut.then_wrapped(
|
||||
[rest = std::make_tuple(std::move(rest)...)] (Future&& fut) mutable {
|
||||
return apply(do_when_all(), std::move(rest)).then_wrapped(
|
||||
[fut = std::move(fut)] (future<std::tuple<Rest...>>&& rest) mutable {
|
||||
return make_ready_future<std::tuple<Future, Rest...>>(
|
||||
std::tuple_cat(std::make_tuple(std::move(fut)), std::get<0>(rest.get())));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// \cond internal
|
||||
template <typename Iterator, typename IteratorCategory>
|
||||
inline
|
||||
size_t
|
||||
when_all_estimate_vector_capacity(Iterator begin, Iterator end, IteratorCategory category) {
|
||||
// For InputIterators we can't estimate needed capacity
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
inline
|
||||
size_t
|
||||
when_all_estimate_vector_capacity(Iterator begin, Iterator end, std::forward_iterator_tag category) {
|
||||
// May be linear time below random_access_iterator_tag, but still better than reallocation
|
||||
return std::distance(begin, end);
|
||||
}
|
||||
|
||||
// Internal function for when_all().
|
||||
template <typename Future>
|
||||
inline
|
||||
future<std::vector<Future>>
|
||||
complete_when_all(std::vector<Future>&& futures, typename std::vector<Future>::iterator pos) {
|
||||
// If any futures are already ready, skip them.
|
||||
while (pos != futures.end() && pos->available()) {
|
||||
++pos;
|
||||
}
|
||||
// Done?
|
||||
if (pos == futures.end()) {
|
||||
return make_ready_future<std::vector<Future>>(std::move(futures));
|
||||
}
|
||||
// Wait for unready future, store, and continue.
|
||||
return pos->then_wrapped([futures = std::move(futures), pos] (auto fut) mutable {
|
||||
*pos++ = std::move(fut);
|
||||
return complete_when_all(std::move(futures), pos);
|
||||
});
|
||||
}
|
||||
/// \endcond
|
||||
|
||||
/// Wait for many futures to complete, capturing possible errors (iterator version).
|
||||
///
|
||||
/// Given a range of futures as input, wait for all of them
|
||||
/// to resolve (either successfully or with an exception), and return
|
||||
/// them as a \c std::vector so individual values or exceptions can be examined.
|
||||
///
|
||||
/// \param begin an \c InputIterator designating the beginning of the range of futures
|
||||
/// \param end an \c InputIterator designating the end of the range of futures
|
||||
/// \return an \c std::vector<> of all the futures in the input; when
|
||||
/// ready, all contained futures will be ready as well.
|
||||
template <typename FutureIterator>
|
||||
inline
|
||||
future<std::vector<typename std::iterator_traits<FutureIterator>::value_type>>
|
||||
when_all(FutureIterator begin, FutureIterator end) {
|
||||
using itraits = std::iterator_traits<FutureIterator>;
|
||||
std::vector<typename itraits::value_type> ret;
|
||||
ret.reserve(when_all_estimate_vector_capacity(begin, end, typename itraits::iterator_category()));
|
||||
// Important to invoke the *begin here, in case it's a function iterator,
|
||||
// so we launch all computation in parallel.
|
||||
std::move(begin, end, std::back_inserter(ret));
|
||||
return complete_when_all(std::move(ret), ret.begin());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct reducer_with_get_traits {
|
||||
using result_type = decltype(std::declval<T>().get());
|
||||
using future_type = future<result_type>;
|
||||
static future_type maybe_call_get(future<> f, lw_shared_ptr<T> r) {
|
||||
return f.then([r = std::move(r)] () mutable {
|
||||
return make_ready_future<result_type>(std::move(*r).get());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename V = void>
|
||||
struct reducer_traits {
|
||||
using future_type = future<>;
|
||||
static future_type maybe_call_get(future<> f, lw_shared_ptr<T> r) {
|
||||
return f.then([r = std::move(r)] {});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct reducer_traits<T, decltype(std::declval<T>().get(), void())> : public reducer_with_get_traits<T> {};
|
||||
|
||||
// @Mapper is a callable which transforms values from the iterator range
|
||||
// into a future<T>. @Reducer is an object which can be called with T as
|
||||
// parameter and yields a future<>. It may have a get() method which returns
|
||||
// a value of type U which holds the result of reduction. This value is wrapped
|
||||
// in a future and returned by this function. If the reducer has no get() method
|
||||
// then this function returns future<>.
|
||||
//
|
||||
// TODO: specialize for non-deferring reducer
|
||||
template <typename Iterator, typename Mapper, typename Reducer>
|
||||
inline
|
||||
auto
|
||||
map_reduce(Iterator begin, Iterator end, Mapper&& mapper, Reducer&& r)
|
||||
-> typename reducer_traits<Reducer>::future_type
|
||||
{
|
||||
auto r_ptr = make_lw_shared(std::forward<Reducer>(r));
|
||||
future<> ret = make_ready_future<>();
|
||||
while (begin != end) {
|
||||
ret = mapper(*begin++).then([ret = std::move(ret), r_ptr] (auto value) mutable {
|
||||
return ret.then([value = std::move(value), r_ptr] () mutable {
|
||||
return (*r_ptr)(std::move(value));
|
||||
});
|
||||
});
|
||||
}
|
||||
return reducer_traits<Reducer>::maybe_call_get(std::move(ret), r_ptr);
|
||||
}
|
||||
|
||||
/// Asynchronous map/reduce transformation.
|
||||
///
|
||||
/// Given a range of objects, an asynchronous unary function
|
||||
/// operating on these objects, an initial value, and a
|
||||
/// binary function for reducing, map_reduce() will
|
||||
/// transform each object in the range, then apply
|
||||
/// the the reducing function to the result.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// Calculate the total size of several files:
|
||||
///
|
||||
/// \code
|
||||
/// map_reduce(files.begin(), files.end(),
|
||||
/// std::mem_fn(file::size),
|
||||
/// size_t(0),
|
||||
/// std::plus<size_t>())
|
||||
/// \endcode
|
||||
///
|
||||
/// Requirements:
|
||||
/// - Iterator: an InputIterator.
|
||||
/// - Mapper: unary function taking Iterator::value_type and producing a future<...>.
|
||||
/// - Initial: any value type
|
||||
/// - Reduce: a binary function taking two Initial values and returning an Initial
|
||||
///
|
||||
/// Return type:
|
||||
/// - future<Initial>
|
||||
///
|
||||
/// \param begin beginning of object range to operate on
|
||||
/// \param end end of object range to operate on
|
||||
/// \param mapper map function to call on each object, returning a future
|
||||
/// \param initial initial input value to reduce function
|
||||
/// \param reduce binary function for merging two result values from \c mapper
|
||||
///
|
||||
/// \return equivalent to \c reduce(reduce(initial, mapper(obj0)), mapper(obj1)) ...
|
||||
template <typename Iterator, typename Mapper, typename Initial, typename Reduce>
|
||||
inline
|
||||
future<Initial>
|
||||
map_reduce(Iterator begin, Iterator end, Mapper&& mapper, Initial initial, Reduce reduce) {
|
||||
struct state {
|
||||
Initial result;
|
||||
Reduce reduce;
|
||||
};
|
||||
auto s = make_lw_shared(state{std::move(initial), std::move(reduce)});
|
||||
future<> ret = make_ready_future<>();
|
||||
while (begin != end) {
|
||||
ret = mapper(*begin++).then([s = s.get(), ret = std::move(ret)] (auto&& value) mutable {
|
||||
s->result = s->reduce(std::move(s->result), std::move(value));
|
||||
return std::move(ret);
|
||||
});
|
||||
}
|
||||
return ret.then([s] {
|
||||
return make_ready_future<Initial>(std::move(s->result));
|
||||
});
|
||||
}
|
||||
|
||||
/// Asynchronous map/reduce transformation (range version).
|
||||
///
|
||||
/// Given a range of objects, an asynchronous unary function
|
||||
/// operating on these objects, an initial value, and a
|
||||
/// binary function for reducing, map_reduce() will
|
||||
/// transform each object in the range, then apply
|
||||
/// the the reducing function to the result.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// Calculate the total size of several files:
|
||||
///
|
||||
/// \code
|
||||
/// std::vector<file> files = ...;
|
||||
/// map_reduce(files,
|
||||
/// std::mem_fn(file::size),
|
||||
/// size_t(0),
|
||||
/// std::plus<size_t>())
|
||||
/// \endcode
|
||||
///
|
||||
/// Requirements:
|
||||
/// - Iterator: an InputIterator.
|
||||
/// - Mapper: unary function taking Iterator::value_type and producing a future<...>.
|
||||
/// - Initial: any value type
|
||||
/// - Reduce: a binary function taking two Initial values and returning an Initial
|
||||
///
|
||||
/// Return type:
|
||||
/// - future<Initial>
|
||||
///
|
||||
/// \param range object range to operate on
|
||||
/// \param mapper map function to call on each object, returning a future
|
||||
/// \param initial initial input value to reduce function
|
||||
/// \param reduce binary function for merging two result values from \c mapper
|
||||
///
|
||||
/// \return equivalent to \c reduce(reduce(initial, mapper(obj0)), mapper(obj1)) ...
|
||||
template <typename Range, typename Mapper, typename Initial, typename Reduce>
|
||||
inline
|
||||
future<Initial>
|
||||
map_reduce(Range&& range, Mapper&& mapper, Initial initial, Reduce reduce) {
|
||||
return map_reduce(std::begin(range), std::end(range), std::forward<Mapper>(mapper),
|
||||
std::move(initial), std::move(reduce));
|
||||
}
|
||||
|
||||
// Implements @Reducer concept. Calculates the result by
|
||||
// adding elements to the accumulator.
|
||||
template <typename Result, typename Addend = Result>
|
||||
class adder {
|
||||
private:
|
||||
Result _result;
|
||||
public:
|
||||
future<> operator()(const Addend& value) {
|
||||
_result += value;
|
||||
return make_ready_future<>();
|
||||
}
|
||||
Result get() && {
|
||||
return std::move(_result);
|
||||
}
|
||||
};
|
||||
|
||||
static inline
|
||||
future<> now() {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
#endif /* CORE_FUTURE_UTIL_HH_ */
|
||||
1005
core/future.hh
1005
core/future.hh
File diff suppressed because it is too large
Load Diff
104
core/gate.hh
104
core/gate.hh
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2014 Cloudius Systems
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "future.hh"
|
||||
#include <experimental/optional>
|
||||
#include <exception>
|
||||
|
||||
namespace seastar {
|
||||
|
||||
/// \addtogroup fiber-module
|
||||
/// @{
|
||||
|
||||
namespace stdx = std::experimental;
|
||||
|
||||
/// Exception thrown when a \ref gate object has been closed
|
||||
/// by the \ref gate::close() method.
|
||||
class gate_closed_exception : public std::exception {
|
||||
public:
|
||||
virtual const char* what() const noexcept override {
|
||||
return "gate closed";
|
||||
}
|
||||
};
|
||||
|
||||
/// Facility to stop new requests, and to tell when existing requests are done.
|
||||
///
|
||||
/// When stopping a service that serves asynchronous requests, we are faced with
|
||||
/// two problems: preventing new requests from coming in, and knowing when existing
|
||||
/// requests have completed. The \c gate class provides a solution.
|
||||
class gate {
|
||||
size_t _count = 0;
|
||||
stdx::optional<promise<>> _stopped;
|
||||
public:
|
||||
/// Registers an in-progress request.
|
||||
///
|
||||
/// If the gate is not closed, the request is registered. Otherwise,
|
||||
/// a \ref gate_closed_exception is thrown.
|
||||
void enter() {
|
||||
if (_stopped) {
|
||||
throw gate_closed_exception();
|
||||
}
|
||||
++_count;
|
||||
}
|
||||
/// Unregisters an in-progress request.
|
||||
///
|
||||
/// If the gate is closed, and there are no more in-progress requests,
|
||||
/// the \ref closed() promise will be fulfilled.
|
||||
void leave() {
|
||||
--_count;
|
||||
if (!_count && _stopped) {
|
||||
_stopped->set_value();
|
||||
}
|
||||
}
|
||||
/// Closes the gate.
|
||||
///
|
||||
/// Future calls to \ref enter() will fail with an exception, and when
|
||||
/// all current requests call \ref leave(), the returned future will be
|
||||
/// made ready.
|
||||
future<> close() {
|
||||
_stopped = stdx::make_optional(promise<>());
|
||||
if (!_count) {
|
||||
_stopped->set_value();
|
||||
}
|
||||
return _stopped->get_future();
|
||||
}
|
||||
};
|
||||
|
||||
/// Executes the function \c func making sure the gate \c g is properly entered
|
||||
/// and later on, properly left.
|
||||
///
|
||||
/// \param func function to be executed
|
||||
/// \param g the gate. Caller must make sure that it outlives this function.
|
||||
/// \returns whatever \c func returns
|
||||
///
|
||||
/// \relates gate
|
||||
template <typename Func>
|
||||
inline
|
||||
auto
|
||||
with_gate(gate& g, Func&& func) {
|
||||
g.enter();
|
||||
return func().finally([&g] { g.leave(); });
|
||||
}
|
||||
/// @}
|
||||
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "net/packet.hh"
|
||||
|
||||
template<typename CharType>
|
||||
inline
|
||||
future<> output_stream<CharType>::write(const char_type* buf) {
|
||||
return write(buf, strlen(buf));
|
||||
}
|
||||
|
||||
template<typename CharType>
|
||||
template<typename StringChar, typename SizeType, SizeType MaxSize>
|
||||
inline
|
||||
future<> output_stream<CharType>::write(const basic_sstring<StringChar, SizeType, MaxSize>& s) {
|
||||
return write(reinterpret_cast<const CharType *>(s.c_str()), s.size());
|
||||
}
|
||||
|
||||
template<typename CharType>
|
||||
inline
|
||||
future<> output_stream<CharType>::write(const std::basic_string<CharType>& s) {
|
||||
return write(s.c_str(), s.size());
|
||||
}
|
||||
|
||||
template<typename CharType>
|
||||
future<> output_stream<CharType>::write(scattered_message<CharType> msg) {
|
||||
return write(std::move(msg).release());
|
||||
}
|
||||
|
||||
template<typename CharType>
|
||||
future<> output_stream<CharType>::write(net::packet p) {
|
||||
static_assert(std::is_same<CharType, char>::value, "packet works on char");
|
||||
|
||||
if (p.len() == 0) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
assert(!_end && "Mixing buffered writes and zero-copy writes not supported yet");
|
||||
|
||||
if (!_trim_to_size || p.len() <= _size) {
|
||||
// TODO: aggregate buffers for later coalescing. Currently we flush right
|
||||
// after appending the message anyway, so it doesn't matter.
|
||||
return _fd.put(std::move(p));
|
||||
}
|
||||
|
||||
auto head = p.share(0, _size);
|
||||
p.trim_front(_size);
|
||||
return _fd.put(std::move(head)).then([this, p = std::move(p)] () mutable {
|
||||
return write(std::move(p));
|
||||
});
|
||||
}
|
||||
|
||||
template <typename CharType>
|
||||
future<temporary_buffer<CharType>>
|
||||
input_stream<CharType>::read_exactly_part(size_t n, tmp_buf out, size_t completed) {
|
||||
if (available()) {
|
||||
auto now = std::min(n - completed, available());
|
||||
std::copy(_buf.get(), _buf.get() + now, out.get_write() + completed);
|
||||
_buf.trim_front(now);
|
||||
completed += now;
|
||||
}
|
||||
if (completed == n) {
|
||||
return make_ready_future<tmp_buf>(std::move(out));
|
||||
}
|
||||
|
||||
// _buf is now empty
|
||||
return _fd.get().then([this, n, out = std::move(out), completed] (auto buf) mutable {
|
||||
if (buf.size() == 0) {
|
||||
_eof = true;
|
||||
return make_ready_future<tmp_buf>(std::move(buf));
|
||||
}
|
||||
_buf = std::move(buf);
|
||||
return this->read_exactly_part(n, std::move(out), completed);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename CharType>
|
||||
future<temporary_buffer<CharType>>
|
||||
input_stream<CharType>::read_exactly(size_t n) {
|
||||
if (_buf.size() == n) {
|
||||
// easy case: steal buffer, return to caller
|
||||
return make_ready_future<tmp_buf>(std::move(_buf));
|
||||
} else if (_buf.size() > n) {
|
||||
// buffer large enough, share it with caller
|
||||
auto front = _buf.share(0, n);
|
||||
_buf.trim_front(n);
|
||||
return make_ready_future<tmp_buf>(std::move(front));
|
||||
} else if (_buf.size() == 0) {
|
||||
// buffer is empty: grab one and retry
|
||||
return _fd.get().then([this, n] (auto buf) mutable {
|
||||
if (buf.size() == 0) {
|
||||
_eof = true;
|
||||
return make_ready_future<tmp_buf>(std::move(buf));
|
||||
}
|
||||
_buf = std::move(buf);
|
||||
return this->read_exactly(n);
|
||||
});
|
||||
} else {
|
||||
// buffer too small: start copy/read loop
|
||||
tmp_buf b(n);
|
||||
return read_exactly_part(n, std::move(b), 0);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CharType>
|
||||
template <typename Consumer>
|
||||
future<>
|
||||
input_stream<CharType>::consume(Consumer& consumer) {
|
||||
for (;;) {
|
||||
if (_buf.empty() && !_eof) {
|
||||
return _fd.get().then([this, &consumer] (tmp_buf buf) {
|
||||
_buf = std::move(buf);
|
||||
_eof = _buf.empty();
|
||||
return consume(consumer);
|
||||
});
|
||||
}
|
||||
future<unconsumed_remainder> unconsumed = consumer(std::move(_buf));
|
||||
if (unconsumed.available()) {
|
||||
unconsumed_remainder u = std::get<0>(unconsumed.get());
|
||||
if (u) {
|
||||
// consumer is done
|
||||
_buf = std::move(u.value());
|
||||
return make_ready_future<>();
|
||||
}
|
||||
if (_eof) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
// If we're here, consumer consumed entire buffer and is ready for
|
||||
// more now. So we do not return, and rather continue the loop.
|
||||
// TODO: if we did too many iterations, schedule a call to
|
||||
// consume() instead of continuing the loop.
|
||||
} else {
|
||||
// TODO: here we wait for the consumer to finish the previous
|
||||
// buffer (fulfilling "unconsumed") before starting to read the
|
||||
// next one. Consider reading ahead.
|
||||
return unconsumed.then([this, &consumer] (unconsumed_remainder u) {
|
||||
if (u) {
|
||||
// consumer is done
|
||||
_buf = std::move(u.value());
|
||||
return make_ready_future<>();
|
||||
} else {
|
||||
// consumer consumed entire buffer, and is ready for more
|
||||
return consume(consumer);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Writes @buf in chunks of _size length. The last chunk is buffered if smaller.
|
||||
template <typename CharType>
|
||||
future<>
|
||||
output_stream<CharType>::split_and_put(temporary_buffer<CharType> buf) {
|
||||
assert(_end == 0);
|
||||
|
||||
if (buf.size() < _size) {
|
||||
if (!_buf) {
|
||||
_buf = _fd.allocate_buffer(_size);
|
||||
}
|
||||
std::copy(buf.get(), buf.get() + buf.size(), _buf.get_write());
|
||||
_end = buf.size();
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
auto chunk = buf.share(0, _size);
|
||||
buf.trim_front(_size);
|
||||
return _fd.put(std::move(chunk)).then([this, buf = std::move(buf)] () mutable {
|
||||
return split_and_put(std::move(buf));
|
||||
});
|
||||
}
|
||||
|
||||
template <typename CharType>
|
||||
future<>
|
||||
output_stream<CharType>::write(const char_type* buf, size_t n) {
|
||||
auto bulk_threshold = _end ? (2 * _size - _end) : _size;
|
||||
if (n >= bulk_threshold) {
|
||||
if (_end) {
|
||||
auto now = _size - _end;
|
||||
std::copy(buf, buf + now, _buf.get_write() + _end);
|
||||
_end = _size;
|
||||
temporary_buffer<char> tmp = _fd.allocate_buffer(n - now);
|
||||
std::copy(buf + now, buf + n, tmp.get_write());
|
||||
return flush().then([this, tmp = std::move(tmp)]() mutable {
|
||||
if (_trim_to_size) {
|
||||
return split_and_put(std::move(tmp));
|
||||
} else {
|
||||
return _fd.put(std::move(tmp));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
temporary_buffer<char> tmp = _fd.allocate_buffer(n);
|
||||
std::copy(buf, buf + n, tmp.get_write());
|
||||
if (_trim_to_size) {
|
||||
return split_and_put(std::move(tmp));
|
||||
} else {
|
||||
return _fd.put(std::move(tmp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_buf) {
|
||||
_buf = _fd.allocate_buffer(_size);
|
||||
}
|
||||
|
||||
auto now = std::min(n, _size - _end);
|
||||
std::copy(buf, buf + now, _buf.get_write() + _end);
|
||||
_end += now;
|
||||
if (now == n) {
|
||||
return make_ready_future<>();
|
||||
} else {
|
||||
temporary_buffer<char> next = _fd.allocate_buffer(_size);
|
||||
std::copy(buf + now, buf + n, next.get_write());
|
||||
_end = n - now;
|
||||
std::swap(next, _buf);
|
||||
return _fd.put(std::move(next));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CharType>
|
||||
future<>
|
||||
output_stream<CharType>::flush() {
|
||||
if (!_end) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
_buf.trim(_end);
|
||||
_end = 0;
|
||||
return _fd.put(std::move(_buf));
|
||||
}
|
||||
|
||||
185
core/iostream.hh
185
core/iostream.hh
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
//
|
||||
// Buffered input and output streams
|
||||
//
|
||||
// Two abstract classes (data_source and data_sink) provide means
|
||||
// to acquire bulk data from, or push bulk data to, some provider.
|
||||
// These could be tied to a TCP connection, a disk file, or a memory
|
||||
// buffer.
|
||||
//
|
||||
// Two concrete classes (input_stream and output_stream) buffer data
|
||||
// from data_source and data_sink and provide easier means to process
|
||||
// it.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "future.hh"
|
||||
#include "temporary_buffer.hh"
|
||||
#include "scattered_message.hh"
|
||||
|
||||
namespace net { class packet; }
|
||||
|
||||
class data_source_impl {
|
||||
public:
|
||||
virtual ~data_source_impl() {}
|
||||
virtual future<temporary_buffer<char>> get() = 0;
|
||||
};
|
||||
|
||||
class data_source {
|
||||
std::unique_ptr<data_source_impl> _dsi;
|
||||
protected:
|
||||
data_source_impl* impl() const { return _dsi.get(); }
|
||||
public:
|
||||
data_source() = default;
|
||||
explicit data_source(std::unique_ptr<data_source_impl> dsi) : _dsi(std::move(dsi)) {}
|
||||
data_source(data_source&& x) = default;
|
||||
data_source& operator=(data_source&& x) = default;
|
||||
future<temporary_buffer<char>> get() { return _dsi->get(); }
|
||||
};
|
||||
|
||||
class data_sink_impl {
|
||||
public:
|
||||
virtual ~data_sink_impl() {}
|
||||
virtual temporary_buffer<char> allocate_buffer(size_t size) {
|
||||
return temporary_buffer<char>(size);
|
||||
}
|
||||
virtual future<> put(net::packet data) = 0;
|
||||
virtual future<> put(std::vector<temporary_buffer<char>> data) {
|
||||
net::packet p;
|
||||
p.reserve(data.size());
|
||||
for (auto& buf : data) {
|
||||
p = net::packet(std::move(p), net::fragment{buf.get_write(), buf.size()}, buf.release());
|
||||
}
|
||||
return put(std::move(p));
|
||||
}
|
||||
virtual future<> put(temporary_buffer<char> buf) {
|
||||
return put(net::packet(net::fragment{buf.get_write(), buf.size()}, buf.release()));
|
||||
}
|
||||
virtual future<> close() = 0;
|
||||
};
|
||||
|
||||
class data_sink {
|
||||
std::unique_ptr<data_sink_impl> _dsi;
|
||||
public:
|
||||
data_sink() = default;
|
||||
explicit data_sink(std::unique_ptr<data_sink_impl> dsi) : _dsi(std::move(dsi)) {}
|
||||
data_sink(data_sink&& x) = default;
|
||||
data_sink& operator=(data_sink&& x) = default;
|
||||
temporary_buffer<char> allocate_buffer(size_t size) {
|
||||
return _dsi->allocate_buffer(size);
|
||||
}
|
||||
future<> put(std::vector<temporary_buffer<char>> data) {
|
||||
return _dsi->put(std::move(data));
|
||||
}
|
||||
future<> put(temporary_buffer<char> data) {
|
||||
return _dsi->put(std::move(data));
|
||||
}
|
||||
future<> put(net::packet p) {
|
||||
return _dsi->put(std::move(p));
|
||||
}
|
||||
future<> close() { return _dsi->close(); }
|
||||
};
|
||||
|
||||
template <typename CharType>
|
||||
class input_stream final {
|
||||
static_assert(sizeof(CharType) == 1, "must buffer stream of bytes");
|
||||
data_source _fd;
|
||||
temporary_buffer<CharType> _buf;
|
||||
bool _eof = false;
|
||||
private:
|
||||
using tmp_buf = temporary_buffer<CharType>;
|
||||
size_t available() const { return _buf.size(); }
|
||||
protected:
|
||||
void reset() { _buf = {}; }
|
||||
data_source* fd() { return &_fd; }
|
||||
public:
|
||||
// Consumer concept, for consume() method:
|
||||
using unconsumed_remainder = std::experimental::optional<tmp_buf>;
|
||||
struct ConsumerConcept {
|
||||
// The consumer should operate on the data given to it, and
|
||||
// return a future "unconsumed remainder", which can be undefined
|
||||
// if the consumer consumed all the input given to it and is ready
|
||||
// for more, or defined when the consumer is done (and in that case
|
||||
// the value is the unconsumed part of the last data buffer - this
|
||||
// can also happen to be empty).
|
||||
future<unconsumed_remainder> operator()(tmp_buf data);
|
||||
};
|
||||
using char_type = CharType;
|
||||
input_stream() = default;
|
||||
explicit input_stream(data_source fd) : _fd(std::move(fd)), _buf(0) {}
|
||||
input_stream(input_stream&&) = default;
|
||||
input_stream& operator=(input_stream&&) = default;
|
||||
future<temporary_buffer<CharType>> read_exactly(size_t n);
|
||||
template <typename Consumer>
|
||||
future<> consume(Consumer& c);
|
||||
bool eof() { return _eof; }
|
||||
private:
|
||||
future<temporary_buffer<CharType>> read_exactly_part(size_t n, tmp_buf buf, size_t completed);
|
||||
};
|
||||
|
||||
// Facilitates data buffering before it's handed over to data_sink.
|
||||
//
|
||||
// When trim_to_size is true it's guaranteed that data sink will not receive
|
||||
// chunks larger than the configured size, which could be the case when a
|
||||
// single write call is made with data larger than the configured size.
|
||||
//
|
||||
// The data sink will not receive empty chunks.
|
||||
//
|
||||
template <typename CharType>
|
||||
class output_stream final {
|
||||
static_assert(sizeof(CharType) == 1, "must buffer stream of bytes");
|
||||
data_sink _fd;
|
||||
temporary_buffer<CharType> _buf;
|
||||
size_t _size = 0;
|
||||
size_t _begin = 0;
|
||||
size_t _end = 0;
|
||||
bool _trim_to_size = false;
|
||||
private:
|
||||
size_t available() const { return _end - _begin; }
|
||||
size_t possibly_available() const { return _size - _begin; }
|
||||
future<> split_and_put(temporary_buffer<CharType> buf);
|
||||
public:
|
||||
using char_type = CharType;
|
||||
output_stream() = default;
|
||||
output_stream(data_sink fd, size_t size, bool trim_to_size = false)
|
||||
: _fd(std::move(fd)), _size(size), _trim_to_size(trim_to_size) {}
|
||||
output_stream(output_stream&&) = default;
|
||||
output_stream& operator=(output_stream&&) = default;
|
||||
future<> write(const char_type* buf, size_t n);
|
||||
future<> write(const char_type* buf);
|
||||
|
||||
template <typename StringChar, typename SizeType, SizeType MaxSize>
|
||||
future<> write(const basic_sstring<StringChar, SizeType, MaxSize>& s);
|
||||
future<> write(const std::basic_string<char_type>& s);
|
||||
|
||||
future<> write(net::packet p);
|
||||
future<> write(scattered_message<char_type> msg);
|
||||
future<> flush();
|
||||
future<> close() { return flush().then([this] { return _fd.close(); }); }
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
#include "iostream-impl.hh"
|
||||
1221
core/memory.cc
1221
core/memory.cc
File diff suppressed because it is too large
Load Diff
175
core/memory.hh
175
core/memory.hh
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef MEMORY_HH_
|
||||
#define MEMORY_HH_
|
||||
|
||||
#include "resource.hh"
|
||||
#include <new>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
|
||||
/// \defgroup memory-module Memory management
|
||||
///
|
||||
/// Functions and classes for managing memory.
|
||||
///
|
||||
/// Memory management in seastar consists of the following:
|
||||
///
|
||||
/// - Low-level memory management in the \ref memory namespace.
|
||||
/// - Various smart pointers: \ref shared_ptr, \ref lw_shared_ptr,
|
||||
/// and \ref foreign_ptr.
|
||||
/// - zero-copy support: \ref temporary_buffer and \ref deleter.
|
||||
|
||||
/// Low-level memory management support
|
||||
///
|
||||
/// The \c memory namespace provides functions and classes for interfacing
|
||||
/// with the seastar memory allocator.
|
||||
///
|
||||
/// The seastar memory allocator splits system memory into a pool per
|
||||
/// logical core (lcore). Memory allocated one an lcore should be freed
|
||||
/// on the same lcore; failing to do so carries a severe performance
|
||||
/// penalty. It is possible to share memory with another core, but this
|
||||
/// should be limited to avoid cache coherency traffic.
|
||||
namespace memory {
|
||||
|
||||
/// \cond internal
|
||||
// TODO: Use getpagesize() in order to learn a size of a system PAGE.
|
||||
static constexpr size_t page_bits = 12;
|
||||
static constexpr size_t page_size = 1 << page_bits; // 4K
|
||||
static constexpr size_t huge_page_size = 512 * page_size; // 2M
|
||||
|
||||
void configure(std::vector<resource::memory> m,
|
||||
std::experimental::optional<std::string> hugetlbfs_path = {});
|
||||
|
||||
void* allocate_reclaimable(size_t size);
|
||||
|
||||
class reclaimer {
|
||||
std::function<void ()> _reclaim;
|
||||
public:
|
||||
reclaimer(std::function<void ()> reclaim);
|
||||
~reclaimer();
|
||||
void do_reclaim() { _reclaim(); }
|
||||
};
|
||||
|
||||
// Call periodically to recycle objects that were freed
|
||||
// on cpu other than the one they were allocated on.
|
||||
//
|
||||
// Returns @true if any work was actually performed.
|
||||
bool drain_cross_cpu_freelist();
|
||||
|
||||
|
||||
// We don't want the memory code calling back into the rest of
|
||||
// the system, so allow the rest of the system to tell the memory
|
||||
// code how to initiate reclaim.
|
||||
//
|
||||
// When memory is low, calling \c hook(fn) will result in fn being called
|
||||
// in a safe place wrt. allocations.
|
||||
void set_reclaim_hook(
|
||||
std::function<void (std::function<void ()>)> hook);
|
||||
|
||||
using physical_address = uint64_t;
|
||||
|
||||
struct translation {
|
||||
translation() = default;
|
||||
translation(physical_address a, size_t s) : addr(a), size(s) {}
|
||||
physical_address addr = 0;
|
||||
size_t size = 0;
|
||||
};
|
||||
|
||||
// Translate a virtual address range to a physical range.
|
||||
//
|
||||
// Can return a smaller range (in which case the reminder needs
|
||||
// to be translated again), or a zero sized range in case the
|
||||
// translation is not known.
|
||||
translation translate(const void* addr, size_t size);
|
||||
|
||||
/// \endcond
|
||||
|
||||
class statistics;
|
||||
|
||||
/// Capture a snapshot of memory allocation statistics for this lcore.
|
||||
statistics stats();
|
||||
|
||||
/// Memory allocation statistics.
|
||||
class statistics {
|
||||
uint64_t _mallocs;
|
||||
uint64_t _frees;
|
||||
uint64_t _cross_cpu_frees;
|
||||
size_t _free_memory;
|
||||
uint64_t _reclaims;
|
||||
private:
|
||||
statistics(uint64_t mallocs, uint64_t frees, uint64_t cross_cpu_frees,
|
||||
uint64_t free_memory, uint64_t reclaims)
|
||||
: _mallocs(mallocs), _frees(frees), _cross_cpu_frees(cross_cpu_frees)
|
||||
, _free_memory(free_memory), _reclaims(reclaims) {}
|
||||
public:
|
||||
/// Total number of memory allocations calls since the system was started.
|
||||
uint64_t mallocs() const { return _mallocs; }
|
||||
/// Total number of memory deallocations calls since the system was started.
|
||||
uint64_t frees() const { return _frees; }
|
||||
/// Total number of memory deallocations that occured on a different lcore
|
||||
/// than the one on which they were allocated.
|
||||
uint64_t cross_cpu_frees() const { return _cross_cpu_frees; }
|
||||
/// Total number of objects which were allocated but not freed.
|
||||
size_t live_objects() const { return mallocs() - frees(); }
|
||||
/// Total free memory (in bytes)
|
||||
size_t free_memory() const { return _free_memory; }
|
||||
/// Number of reclaims performed due to low memory
|
||||
uint64_t reclaims() const { return _reclaims; }
|
||||
friend statistics stats();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
class with_alignment {
|
||||
size_t _align;
|
||||
public:
|
||||
with_alignment(size_t align) : _align(align) {}
|
||||
size_t alignment() const { return _align; }
|
||||
};
|
||||
|
||||
void* operator new(size_t size, with_alignment wa);
|
||||
void* operator new[](size_t size, with_alignment wa);
|
||||
void operator delete(void* ptr, with_alignment wa);
|
||||
void operator delete[](void* ptr, with_alignment wa);
|
||||
|
||||
template <typename T, size_t Align>
|
||||
class aligned_allocator {
|
||||
public:
|
||||
using value_type = T;
|
||||
T* allocate(size_t n) const {
|
||||
// FIXME: multiply can overflow?
|
||||
return reinterpret_cast<T*>(::operator new(n * sizeof(T), with_alignment(Align)));
|
||||
}
|
||||
void deallocate(T* ptr) const {
|
||||
return ::operator delete(ptr, with_alignment(Align));
|
||||
}
|
||||
bool operator==(const aligned_allocator& x) const {
|
||||
return true;
|
||||
}
|
||||
bool operator!=(const aligned_allocator& x) const {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* MEMORY_HH_ */
|
||||
239
core/pipe.hh
239
core/pipe.hh
@@ -1,239 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "future.hh"
|
||||
#include "queue.hh"
|
||||
|
||||
#include <experimental/optional>
|
||||
|
||||
/// \defgroup fiber-module Fibers
|
||||
///
|
||||
/// \brief Fibers of execution
|
||||
///
|
||||
/// Seastar continuations are normally short, but often chained to one
|
||||
/// another, so that one continuation does a bit of work and then schedules
|
||||
/// another continuation for later. Such chains can be long, and often even
|
||||
/// involve loopings - see for example \ref repeat. We call such chains
|
||||
/// "fibers" of execution.
|
||||
///
|
||||
/// These fibers are not threads - each is just a string of continuations -
|
||||
/// but they share some common requirements with traditional threads.
|
||||
/// For example, we want to avoid one fiber getting starved while a second
|
||||
/// fiber continuously runs its continuations one after another.
|
||||
/// As another example, fibers may want to communicate - e.g., one fiber
|
||||
/// produces data that a second fiber consumes, and we wish to ensure that
|
||||
/// both fibers get a chance to run, and that if one stops prematurely,
|
||||
/// the other doesn't hang forever.
|
||||
///
|
||||
/// Consult the following table to see which APIs are useful for fiber tasks:
|
||||
///
|
||||
/// Task | APIs
|
||||
/// -----------------------------------------------|-------------------
|
||||
/// Repeat a blocking task indefinitely | \ref keep_doing()
|
||||
/// Repeat a blocking task, then exit | \ref repeat(), \ref do_until()
|
||||
/// Provide mutual exclusion between two tasks | \ref semaphore, \ref shared_mutex
|
||||
/// Pass a stream of data between two fibers | \ref seastar::pipe
|
||||
/// Safely shut down a resource | \ref seastar::gate, \ref seastar::with_gate()
|
||||
/// Hold on to an object while a fiber is running | \ref do_with()
|
||||
///
|
||||
|
||||
/// Seastar API namespace
|
||||
namespace seastar {
|
||||
|
||||
/// \addtogroup fiber-module
|
||||
/// @{
|
||||
|
||||
class broken_pipe_exception : public std::exception {
|
||||
public:
|
||||
virtual const char* what() const noexcept {
|
||||
return "Broken pipe";
|
||||
}
|
||||
};
|
||||
|
||||
/// \cond internal
|
||||
namespace internal {
|
||||
template <typename T>
|
||||
class pipe_buffer {
|
||||
private:
|
||||
queue<std::experimental::optional<T>> _buf;
|
||||
bool _read_open = true;
|
||||
bool _write_open = true;
|
||||
public:
|
||||
pipe_buffer(size_t size) : _buf(size) {}
|
||||
future<std::experimental::optional<T>> read() {
|
||||
return _buf.pop_eventually();
|
||||
}
|
||||
future<> write(T&& data) {
|
||||
return _buf.push_eventually(std::move(data));
|
||||
}
|
||||
bool readable() const {
|
||||
return _write_open || !_buf.empty();
|
||||
}
|
||||
bool writeable() const {
|
||||
return _read_open;
|
||||
}
|
||||
bool close_read() {
|
||||
// If a writer blocking (on a full queue), need to stop it.
|
||||
if (_buf.full()) {
|
||||
_buf.abort(std::make_exception_ptr(broken_pipe_exception()));
|
||||
}
|
||||
_read_open = false;
|
||||
return !_write_open;
|
||||
}
|
||||
bool close_write() {
|
||||
// If the queue is empty, write the EOF (disengaged optional) to the
|
||||
// queue to wake a blocked reader. If the queue is not empty, there is
|
||||
// no need to write the EOF to the queue - the reader will return an
|
||||
// EOF when it sees that _write_open == false.
|
||||
if (_buf.empty()) {
|
||||
_buf.push({});
|
||||
}
|
||||
_write_open = false;
|
||||
return !_read_open;
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
/// \endcond
|
||||
|
||||
template <typename T>
|
||||
class pipe;
|
||||
|
||||
/// \brief Read side of a \ref seastar::pipe
|
||||
///
|
||||
/// The read side of a pipe, which allows only reading from the pipe.
|
||||
/// A pipe_reader object cannot be created separately, but only as part of a
|
||||
/// reader/writer pair through \ref seastar::pipe.
|
||||
template <typename T>
|
||||
class pipe_reader {
|
||||
private:
|
||||
internal::pipe_buffer<T> *_bufp;
|
||||
pipe_reader(internal::pipe_buffer<T> *bufp) : _bufp(bufp) { }
|
||||
friend class pipe<T>;
|
||||
public:
|
||||
/// \brief Read next item from the pipe
|
||||
///
|
||||
/// Returns a future value, which is fulfilled when the pipe's buffer
|
||||
/// becomes non-empty, or the write side is closed. The value returned
|
||||
/// is an optional<T>, which is disengaged to mark and end of file
|
||||
/// (i.e., the write side was closed, and we've read everything it sent).
|
||||
future<std::experimental::optional<T>> read() {
|
||||
if (_bufp->readable()) {
|
||||
return _bufp->read();
|
||||
} else {
|
||||
return make_ready_future<std::experimental::optional<T>>();
|
||||
}
|
||||
}
|
||||
~pipe_reader() {
|
||||
if (_bufp && _bufp->close_read()) {
|
||||
delete _bufp;
|
||||
}
|
||||
}
|
||||
// Allow move, but not copy, of pipe_reader
|
||||
pipe_reader(pipe_reader&& other) : _bufp(other._bufp) {
|
||||
other._bufp = nullptr;
|
||||
}
|
||||
pipe_reader& operator=(pipe_reader&& other) {
|
||||
std::swap(_bufp, other._bufp);
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Write side of a \ref seastar::pipe
|
||||
///
|
||||
/// The write side of a pipe, which allows only writing to the pipe.
|
||||
/// A pipe_writer object cannot be created separately, but only as part of a
|
||||
/// reader/writer pair through \ref seastar::pipe.
|
||||
template <typename T>
|
||||
class pipe_writer {
|
||||
private:
|
||||
internal::pipe_buffer<T> *_bufp;
|
||||
pipe_writer(internal::pipe_buffer<T> *bufp) : _bufp(bufp) { }
|
||||
friend class pipe<T>;
|
||||
public:
|
||||
/// \brief Write an item to the pipe
|
||||
///
|
||||
/// Returns a future value, which is fulfilled when the data was written
|
||||
/// to the buffer (when it become non-full). If the data could not be
|
||||
/// written because the read side was closed, an exception
|
||||
/// \ref broken_pipe_exception is returned in the future.
|
||||
future<> write(T&& data) {
|
||||
if (_bufp->writeable()) {
|
||||
return _bufp->write(std::move(data));
|
||||
} else {
|
||||
return make_exception_future<>(broken_pipe_exception());
|
||||
}
|
||||
}
|
||||
~pipe_writer() {
|
||||
if (_bufp && _bufp->close_write()) {
|
||||
delete _bufp;
|
||||
}
|
||||
}
|
||||
// Allow move, but not copy, of pipe_writer
|
||||
pipe_writer(pipe_writer&& other) : _bufp(other._bufp) {
|
||||
other._bufp = nullptr;
|
||||
}
|
||||
pipe_writer& operator=(pipe_writer&& other) {
|
||||
std::swap(_bufp, other._bufp);
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief A fixed-size pipe for communicating between two fibers.
|
||||
///
|
||||
/// A pipe<T> is a mechanism to transfer data between two fibers, one
|
||||
/// producing data, and the other consuming it. The fixed-size buffer also
|
||||
/// ensures a balanced execution of the two fibers, because the producer
|
||||
/// fiber blocks when it writes to a full pipe, until the consumer fiber gets
|
||||
/// to run and read from the pipe.
|
||||
///
|
||||
/// A pipe<T> resembles a Unix pipe, in that it has a read side, a write side,
|
||||
/// and a fixed-sized buffer between them, and supports either end to be closed
|
||||
/// independently (and EOF or broken pipe when using the other side).
|
||||
/// A pipe<T> object holds the reader and write sides of the pipe as two
|
||||
/// separate objects. These objects can be moved into two different fibers.
|
||||
/// Importantly, if one of the pipe ends is destroyed (i.e., the continuations
|
||||
/// capturing it end), the other end of the pipe will stop blocking, so the
|
||||
/// other fiber will not hang.
|
||||
///
|
||||
/// The pipe's read and write interfaces are future-based blocking. I.e., the
|
||||
/// write() and read() methods return a future which is fulfilled when the
|
||||
/// operation is complete. The pipe is single-reader single-writer, meaning
|
||||
/// that until the future returned by read() is fulfilled, read() must not be
|
||||
/// called again (and same for write).
|
||||
///
|
||||
/// Note: The pipe reader and writer are movable, but *not* copyable. It is
|
||||
/// often convenient to wrap each end in a shared pointer, so it can be
|
||||
/// copied (e.g., used in an std::function which needs to be copyable) or
|
||||
/// easily captured into multiple continuations.
|
||||
template <typename T>
|
||||
class pipe {
|
||||
public:
|
||||
pipe_reader<T> reader;
|
||||
pipe_writer<T> writer;
|
||||
explicit pipe(size_t size) : pipe(new internal::pipe_buffer<T>(size)) { }
|
||||
private:
|
||||
pipe(internal::pipe_buffer<T> *bufp) : reader(bufp), writer(bufp) { }
|
||||
};
|
||||
|
||||
|
||||
/// @}
|
||||
|
||||
} // namespace seastar
|
||||
116
core/posix.cc
116
core/posix.cc
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#include "posix.hh"
|
||||
#include "align.hh"
|
||||
#include <sys/mman.h>
|
||||
|
||||
file_desc
|
||||
file_desc::temporary(sstring directory) {
|
||||
// FIXME: add O_TMPFILE support one day
|
||||
directory += "/XXXXXX";
|
||||
std::vector<char> templat(directory.c_str(), directory.c_str() + directory.size() + 1);
|
||||
int fd = ::mkstemp(templat.data());
|
||||
throw_system_error_on(fd == -1);
|
||||
int r = ::unlink(templat.data());
|
||||
throw_system_error_on(r == -1); // leaks created file, but what can we do?
|
||||
return file_desc(fd);
|
||||
}
|
||||
|
||||
void mmap_deleter::operator()(void* ptr) const {
|
||||
::munmap(ptr, _size);
|
||||
}
|
||||
|
||||
mmap_area mmap_anonymous(void* addr, size_t length, int prot, int flags) {
|
||||
auto ret = ::mmap(addr, length, prot, flags | MAP_ANONYMOUS, -1, 0);
|
||||
throw_system_error_on(ret == MAP_FAILED);
|
||||
return mmap_area(reinterpret_cast<char*>(ret), mmap_deleter{length});
|
||||
}
|
||||
|
||||
void* posix_thread::start_routine(void* arg) {
|
||||
auto pfunc = reinterpret_cast<std::function<void ()>*>(arg);
|
||||
try {
|
||||
(*pfunc)();
|
||||
} catch (...) {
|
||||
// We're in POSIX thread callback, so all exceptions need to be handled.
|
||||
std::terminate();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
posix_thread::posix_thread(std::function<void ()> func)
|
||||
: posix_thread(attr{}, std::move(func)) {
|
||||
}
|
||||
|
||||
posix_thread::posix_thread(attr a, std::function<void ()> func)
|
||||
: _func(std::make_unique<std::function<void ()>>(std::move(func))) {
|
||||
pthread_attr_t pa;
|
||||
auto r = pthread_attr_init(&pa);
|
||||
if (r) {
|
||||
throw std::system_error(r, std::system_category());
|
||||
}
|
||||
auto stack_size = a._stack_size.size;
|
||||
if (!stack_size) {
|
||||
stack_size = 2 << 20;
|
||||
}
|
||||
// allocate guard area as well
|
||||
_stack = mmap_anonymous(nullptr, stack_size + (4 << 20),
|
||||
PROT_NONE, MAP_PRIVATE | MAP_NORESERVE);
|
||||
auto stack_start = align_up(_stack.get() + 1, 2 << 20);
|
||||
mmap_area real_stack = mmap_anonymous(stack_start, stack_size,
|
||||
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_STACK);
|
||||
real_stack.release(); // protected by @_stack
|
||||
::madvise(stack_start, stack_size, MADV_HUGEPAGE);
|
||||
r = pthread_attr_setstack(&pa, stack_start, stack_size);
|
||||
if (r) {
|
||||
throw std::system_error(r, std::system_category());
|
||||
}
|
||||
r = pthread_create(&_pthread, &pa,
|
||||
&posix_thread::start_routine, _func.get());
|
||||
if (r) {
|
||||
throw std::system_error(r, std::system_category());
|
||||
}
|
||||
}
|
||||
|
||||
posix_thread::posix_thread(posix_thread&& x)
|
||||
: _func(std::move(x._func)), _pthread(x._pthread), _valid(x._valid)
|
||||
, _stack(std::move(x._stack)) {
|
||||
x._valid = false;
|
||||
}
|
||||
|
||||
posix_thread::~posix_thread() {
|
||||
assert(!_valid);
|
||||
}
|
||||
|
||||
void posix_thread::join() {
|
||||
assert(_valid);
|
||||
pthread_join(_pthread, NULL);
|
||||
_valid = false;
|
||||
}
|
||||
|
||||
void pin_this_thread(unsigned cpu_id) {
|
||||
cpu_set_t cs;
|
||||
CPU_ZERO(&cs);
|
||||
CPU_SET(cpu_id, &cs);
|
||||
auto r = pthread_setaffinity_np(pthread_self(), sizeof(cs), &cs);
|
||||
assert(r == 0);
|
||||
}
|
||||
|
||||
385
core/posix.hh
385
core/posix.hh
@@ -1,385 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef FILE_DESC_HH_
|
||||
#define FILE_DESC_HH_
|
||||
|
||||
#include "sstring.hh"
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <utility>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <signal.h>
|
||||
#include <system_error>
|
||||
#include <boost/optional.hpp>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <memory>
|
||||
#include "net/api.hh"
|
||||
|
||||
inline void throw_system_error_on(bool condition);
|
||||
|
||||
template <typename T>
|
||||
inline void throw_kernel_error(T r);
|
||||
|
||||
struct mmap_deleter {
|
||||
size_t _size;
|
||||
void operator()(void* ptr) const;
|
||||
};
|
||||
|
||||
using mmap_area = std::unique_ptr<char[], mmap_deleter>;
|
||||
|
||||
mmap_area mmap_anonymous(void* addr, size_t length, int prot, int flags);
|
||||
|
||||
class file_desc {
|
||||
int _fd;
|
||||
public:
|
||||
file_desc() = delete;
|
||||
file_desc(const file_desc&) = delete;
|
||||
file_desc(file_desc&& x) : _fd(x._fd) { x._fd = -1; }
|
||||
~file_desc() { if (_fd != -1) { ::close(_fd); } }
|
||||
void operator=(const file_desc&) = delete;
|
||||
file_desc& operator=(file_desc&& x) {
|
||||
if (this != &x) {
|
||||
std::swap(_fd, x._fd);
|
||||
if (x._fd != -1) {
|
||||
x.close();
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
void close() {
|
||||
assert(_fd != -1);
|
||||
auto r = ::close(_fd);
|
||||
throw_system_error_on(r == -1);
|
||||
_fd = -1;
|
||||
}
|
||||
int get() const { return _fd; }
|
||||
static file_desc open(sstring name, int flags, mode_t mode = 0) {
|
||||
int fd = ::open(name.c_str(), flags, mode);
|
||||
throw_system_error_on(fd == -1);
|
||||
return file_desc(fd);
|
||||
}
|
||||
static file_desc socket(int family, int type, int protocol = 0) {
|
||||
int fd = ::socket(family, type, protocol);
|
||||
throw_system_error_on(fd == -1);
|
||||
return file_desc(fd);
|
||||
}
|
||||
static file_desc eventfd(unsigned initval, int flags) {
|
||||
int fd = ::eventfd(initval, flags);
|
||||
throw_system_error_on(fd == -1);
|
||||
return file_desc(fd);
|
||||
}
|
||||
static file_desc epoll_create(int flags = 0) {
|
||||
int fd = ::epoll_create1(flags);
|
||||
throw_system_error_on(fd == -1);
|
||||
return file_desc(fd);
|
||||
}
|
||||
static file_desc timerfd_create(int clockid, int flags) {
|
||||
int fd = ::timerfd_create(clockid, flags);
|
||||
throw_system_error_on(fd == -1);
|
||||
return file_desc(fd);
|
||||
}
|
||||
static file_desc temporary(sstring directory);
|
||||
file_desc dup() const {
|
||||
int fd = ::dup(get());
|
||||
throw_system_error_on(fd == -1);
|
||||
return file_desc(fd);
|
||||
}
|
||||
file_desc accept(sockaddr& sa, socklen_t& sl, int flags = 0) {
|
||||
auto ret = ::accept4(_fd, &sa, &sl, flags);
|
||||
throw_system_error_on(ret == -1);
|
||||
return file_desc(ret);
|
||||
}
|
||||
void shutdown(int how) {
|
||||
auto ret = ::shutdown(_fd, how);
|
||||
throw_system_error_on(ret == -1);
|
||||
}
|
||||
void truncate(size_t size) {
|
||||
auto ret = ::ftruncate(_fd, size);
|
||||
throw_system_error_on(ret);
|
||||
}
|
||||
int ioctl(int request) {
|
||||
return ioctl(request, 0);
|
||||
}
|
||||
int ioctl(int request, int value) {
|
||||
int r = ::ioctl(_fd, request, value);
|
||||
throw_system_error_on(r == -1);
|
||||
return r;
|
||||
}
|
||||
int ioctl(int request, unsigned int value) {
|
||||
int r = ::ioctl(_fd, request, value);
|
||||
throw_system_error_on(r == -1);
|
||||
return r;
|
||||
}
|
||||
template <class X>
|
||||
int ioctl(int request, X& data) {
|
||||
int r = ::ioctl(_fd, request, &data);
|
||||
throw_system_error_on(r == -1);
|
||||
return r;
|
||||
}
|
||||
template <class X>
|
||||
int ioctl(int request, X&& data) {
|
||||
int r = ::ioctl(_fd, request, &data);
|
||||
throw_system_error_on(r == -1);
|
||||
return r;
|
||||
}
|
||||
template <class X>
|
||||
int setsockopt(int level, int optname, X&& data) {
|
||||
int r = ::setsockopt(_fd, level, optname, &data, sizeof(data));
|
||||
throw_system_error_on(r == -1);
|
||||
return r;
|
||||
}
|
||||
int setsockopt(int level, int optname, const char* data) {
|
||||
int r = ::setsockopt(_fd, level, optname, data, strlen(data) + 1);
|
||||
throw_system_error_on(r == -1);
|
||||
return r;
|
||||
}
|
||||
template <class X>
|
||||
int getsockopt(int level, int optname, X&& data) {
|
||||
socklen_t len = sizeof(data);
|
||||
int r = ::getsockopt(_fd, level, optname, &data, &len);
|
||||
throw_system_error_on(r == -1);
|
||||
return r;
|
||||
}
|
||||
int getsockopt(int level, int optname, char* data, socklen_t len) {
|
||||
int r = ::getsockopt(_fd, level, optname, data, &len);
|
||||
throw_system_error_on(r == -1);
|
||||
return r;
|
||||
}
|
||||
size_t size() {
|
||||
struct stat buf;
|
||||
auto r = ::fstat(_fd, &buf);
|
||||
throw_system_error_on(r == -1);
|
||||
return buf.st_size;
|
||||
}
|
||||
boost::optional<size_t> read(void* buffer, size_t len) {
|
||||
auto r = ::read(_fd, buffer, len);
|
||||
if (r == -1 && errno == EAGAIN) {
|
||||
return {};
|
||||
}
|
||||
throw_system_error_on(r == -1);
|
||||
return { size_t(r) };
|
||||
}
|
||||
boost::optional<ssize_t> recv(void* buffer, size_t len, int flags) {
|
||||
auto r = ::recv(_fd, buffer, len, flags);
|
||||
if (r == -1 && errno == EAGAIN) {
|
||||
return {};
|
||||
}
|
||||
throw_system_error_on(r == -1);
|
||||
return { ssize_t(r) };
|
||||
}
|
||||
boost::optional<size_t> recvmsg(msghdr* mh, int flags) {
|
||||
auto r = ::recvmsg(_fd, mh, flags);
|
||||
if (r == -1 && errno == EAGAIN) {
|
||||
return {};
|
||||
}
|
||||
throw_system_error_on(r == -1);
|
||||
return { size_t(r) };
|
||||
}
|
||||
boost::optional<size_t> send(const void* buffer, size_t len, int flags) {
|
||||
auto r = ::send(_fd, buffer, len, flags);
|
||||
if (r == -1 && errno == EAGAIN) {
|
||||
return {};
|
||||
}
|
||||
throw_system_error_on(r == -1);
|
||||
return { size_t(r) };
|
||||
}
|
||||
boost::optional<size_t> sendto(socket_address& addr, const void* buf, size_t len, int flags) {
|
||||
auto r = ::sendto(_fd, buf, len, flags, &addr.u.sa, sizeof(addr.u.sas));
|
||||
if (r == -1 && errno == EAGAIN) {
|
||||
return {};
|
||||
}
|
||||
throw_system_error_on(r == -1);
|
||||
return { size_t(r) };
|
||||
}
|
||||
boost::optional<size_t> sendmsg(const msghdr* msg, int flags) {
|
||||
auto r = ::sendmsg(_fd, msg, flags);
|
||||
if (r == -1 && errno == EAGAIN) {
|
||||
return {};
|
||||
}
|
||||
throw_system_error_on(r == -1);
|
||||
return { size_t(r) };
|
||||
}
|
||||
void bind(sockaddr& sa, socklen_t sl) {
|
||||
auto r = ::bind(_fd, &sa, sl);
|
||||
throw_system_error_on(r == -1);
|
||||
}
|
||||
void connect(sockaddr& sa, socklen_t sl) {
|
||||
auto r = ::connect(_fd, &sa, sl);
|
||||
if (r == -1 && errno == EINPROGRESS) {
|
||||
return;
|
||||
}
|
||||
throw_system_error_on(r == -1);
|
||||
}
|
||||
socket_address get_address() {
|
||||
socket_address addr;
|
||||
auto len = (socklen_t) sizeof(addr.u.sas);
|
||||
auto r = ::getsockname(_fd, &addr.u.sa, &len);
|
||||
throw_system_error_on(r == -1);
|
||||
return addr;
|
||||
}
|
||||
void listen(int backlog) {
|
||||
auto fd = ::listen(_fd, backlog);
|
||||
throw_system_error_on(fd == -1);
|
||||
}
|
||||
boost::optional<size_t> write(const void* buf, size_t len) {
|
||||
auto r = ::write(_fd, buf, len);
|
||||
if (r == -1 && errno == EAGAIN) {
|
||||
return {};
|
||||
}
|
||||
throw_system_error_on(r == -1);
|
||||
return { size_t(r) };
|
||||
}
|
||||
boost::optional<size_t> writev(const iovec *iov, int iovcnt) {
|
||||
auto r = ::writev(_fd, iov, iovcnt);
|
||||
if (r == -1 && errno == EAGAIN) {
|
||||
return {};
|
||||
}
|
||||
throw_system_error_on(r == -1);
|
||||
return { size_t(r) };
|
||||
}
|
||||
size_t pread(void* buf, size_t len, off_t off) {
|
||||
auto r = ::pread(_fd, buf, len, off);
|
||||
throw_system_error_on(r == -1);
|
||||
return size_t(r);
|
||||
}
|
||||
void timerfd_settime(int flags, const itimerspec& its) {
|
||||
auto r = ::timerfd_settime(_fd, flags, &its, NULL);
|
||||
throw_system_error_on(r == -1);
|
||||
}
|
||||
|
||||
mmap_area map(size_t size, unsigned prot, unsigned flags, size_t offset,
|
||||
void* addr = nullptr) {
|
||||
void *x = mmap(addr, size, prot, flags, _fd, offset);
|
||||
throw_system_error_on(x == MAP_FAILED);
|
||||
return mmap_area(static_cast<char*>(x), mmap_deleter{size});
|
||||
}
|
||||
|
||||
mmap_area map_shared_rw(size_t size, size_t offset) {
|
||||
return map(size, PROT_READ | PROT_WRITE, MAP_SHARED, offset);
|
||||
}
|
||||
|
||||
mmap_area map_shared_ro(size_t size, size_t offset) {
|
||||
return map(size, PROT_READ, MAP_SHARED, offset);
|
||||
}
|
||||
|
||||
mmap_area map_private_rw(size_t size, size_t offset) {
|
||||
return map(size, PROT_READ | PROT_WRITE, MAP_PRIVATE, offset);
|
||||
}
|
||||
|
||||
mmap_area map_private_ro(size_t size, size_t offset) {
|
||||
return map(size, PROT_READ, MAP_PRIVATE, offset);
|
||||
}
|
||||
|
||||
private:
|
||||
file_desc(int fd) : _fd(fd) {}
|
||||
};
|
||||
|
||||
class posix_thread {
|
||||
public:
|
||||
class attr;
|
||||
private:
|
||||
// must allocate, since this class is moveable
|
||||
std::unique_ptr<std::function<void ()>> _func;
|
||||
pthread_t _pthread;
|
||||
bool _valid = true;
|
||||
mmap_area _stack;
|
||||
private:
|
||||
static void* start_routine(void* arg);
|
||||
public:
|
||||
posix_thread(std::function<void ()> func);
|
||||
posix_thread(attr a, std::function<void ()> func);
|
||||
posix_thread(posix_thread&& x);
|
||||
~posix_thread();
|
||||
void join();
|
||||
public:
|
||||
class attr {
|
||||
public:
|
||||
struct stack_size { size_t size = 0; };
|
||||
attr() = default;
|
||||
template <typename... A>
|
||||
attr(A... a) {
|
||||
set(std::forward<A>(a)...);
|
||||
}
|
||||
void set() {}
|
||||
template <typename A, typename... Rest>
|
||||
void set(A a, Rest... rest) {
|
||||
set(std::forward<A>(a));
|
||||
set(std::forward<Rest>(rest)...);
|
||||
}
|
||||
void set(stack_size ss) { _stack_size = ss; }
|
||||
private:
|
||||
stack_size _stack_size;
|
||||
friend class posix_thread;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
inline
|
||||
void throw_system_error_on(bool condition) {
|
||||
if (condition) {
|
||||
throw std::system_error(errno, std::system_category());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
void throw_kernel_error(T r) {
|
||||
static_assert(std::is_signed<T>::value, "kernel error variables must be signed");
|
||||
if (r < 0) {
|
||||
throw std::system_error(-r, std::system_category());
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
sigset_t make_sigset_mask(int signo) {
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, signo);
|
||||
return set;
|
||||
}
|
||||
|
||||
inline
|
||||
sigset_t make_full_sigset_mask() {
|
||||
sigset_t set;
|
||||
sigfillset(&set);
|
||||
return set;
|
||||
}
|
||||
|
||||
inline
|
||||
sigset_t make_empty_sigset_mask() {
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
return set;
|
||||
}
|
||||
|
||||
void pin_this_thread(unsigned cpu_id);
|
||||
|
||||
#endif /* FILE_DESC_HH_ */
|
||||
114
core/prefetch.hh
114
core/prefetch.hh
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef PREFETCH_HH_
|
||||
#define PREFETCH_HH_
|
||||
|
||||
#include <atomic>
|
||||
#include <boost/mpl/range_c.hpp>
|
||||
#include <boost/mpl/for_each.hpp>
|
||||
#include "align.hh"
|
||||
|
||||
static constexpr size_t cacheline_size = 64;
|
||||
template <size_t N, int RW, int LOC>
|
||||
struct prefetcher;
|
||||
|
||||
template<int RW, int LOC>
|
||||
struct prefetcher<0, RW, LOC> {
|
||||
prefetcher(uintptr_t ptr) {}
|
||||
};
|
||||
|
||||
template <size_t N, int RW, int LOC>
|
||||
struct prefetcher {
|
||||
prefetcher(uintptr_t ptr) {
|
||||
__builtin_prefetch(reinterpret_cast<void*>(ptr), RW, LOC);
|
||||
std::atomic_signal_fence(std::memory_order_seq_cst);
|
||||
prefetcher<N-64, RW, LOC>(ptr + 64);
|
||||
}
|
||||
};
|
||||
|
||||
// LOC is a locality from __buitin_prefetch() gcc documentation:
|
||||
// "The value locality must be a compile-time constant integer between zero and three. A value of
|
||||
// zero means that the data has no temporal locality, so it need not be left in the cache after
|
||||
// the access. A value of three means that the data has a high degree of temporal locality and
|
||||
// should be left in all levels of cache possible. Values of one and two mean, respectively, a
|
||||
// low or moderate degree of temporal locality. The default is three."
|
||||
template<typename T, int LOC = 3>
|
||||
void prefetch(T* ptr) {
|
||||
prefetcher<align_up(sizeof(T), cacheline_size), 0, LOC>(reinterpret_cast<uintptr_t>(ptr));
|
||||
}
|
||||
|
||||
template<typename Iterator, int LOC = 3>
|
||||
void prefetch(Iterator begin, Iterator end) {
|
||||
std::for_each(begin, end, [] (auto v) { prefetch<decltype(*v), LOC>(v); });
|
||||
}
|
||||
|
||||
template<size_t C, typename T, int LOC = 3>
|
||||
void prefetch_n(T** pptr) {
|
||||
boost::mpl::for_each< boost::mpl::range_c<size_t,0,C> >( [pptr] (size_t x) { prefetch<T, LOC>(*(pptr + x)); } );
|
||||
}
|
||||
|
||||
template<size_t L, int LOC = 3>
|
||||
void prefetch(void* ptr) {
|
||||
prefetcher<L*cacheline_size, 0, LOC>(reinterpret_cast<uintptr_t>(ptr));
|
||||
}
|
||||
|
||||
template<size_t L, typename Iterator, int LOC = 3>
|
||||
void prefetch_n(Iterator begin, Iterator end) {
|
||||
std::for_each(begin, end, [] (auto v) { prefetch<L, LOC>(v); });
|
||||
}
|
||||
|
||||
template<size_t L, size_t C, typename T, int LOC = 3>
|
||||
void prefetch_n(T** pptr) {
|
||||
boost::mpl::for_each< boost::mpl::range_c<size_t,0,C> >( [pptr] (size_t x) { prefetch<L, LOC>(*(pptr + x)); } );
|
||||
}
|
||||
|
||||
template<typename T, int LOC = 3>
|
||||
void prefetchw(T* ptr) {
|
||||
prefetcher<align_up(sizeof(T), cacheline_size), 1, LOC>(reinterpret_cast<uintptr_t>(ptr));
|
||||
}
|
||||
|
||||
template<typename Iterator, int LOC = 3>
|
||||
void prefetchw_n(Iterator begin, Iterator end) {
|
||||
std::for_each(begin, end, [] (auto v) { prefetchw<decltype(*v), LOC>(v); });
|
||||
}
|
||||
|
||||
template<size_t C, typename T, int LOC = 3>
|
||||
void prefetchw_n(T** pptr) {
|
||||
boost::mpl::for_each< boost::mpl::range_c<size_t,0,C> >( [pptr] (size_t x) { prefetchw<T, LOC>(*(pptr + x)); } );
|
||||
}
|
||||
|
||||
template<size_t L, int LOC = 3>
|
||||
void prefetchw(void* ptr) {
|
||||
prefetcher<L*cacheline_size, 1, LOC>(reinterpret_cast<uintptr_t>(ptr));
|
||||
}
|
||||
|
||||
template<size_t L, typename Iterator, int LOC = 3>
|
||||
void prefetchw_n(Iterator begin, Iterator end) {
|
||||
std::for_each(begin, end, [] (auto v) { prefetchw<L, LOC>(v); });
|
||||
}
|
||||
|
||||
template<size_t L, size_t C, typename T, int LOC = 3>
|
||||
void prefetchw_n(T** pptr) {
|
||||
boost::mpl::for_each< boost::mpl::range_c<size_t,0,C> >( [pptr] (size_t x) { prefetchw<L, LOC>(*(pptr + x)); } );
|
||||
}
|
||||
|
||||
#endif
|
||||
125
core/print.hh
125
core/print.hh
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef PRINT_HH_
|
||||
#define PRINT_HH_
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
#include "core/sstring.hh"
|
||||
|
||||
inline void
|
||||
apply_format(boost::format& fmt) {
|
||||
}
|
||||
|
||||
template <typename A0, typename... Arest>
|
||||
inline void
|
||||
apply_format(boost::format& fmt, A0&& a0, Arest&&... arest) {
|
||||
apply_format(fmt % std::forward<A0>(a0), std::forward<Arest>(arest)...);
|
||||
}
|
||||
|
||||
template <typename... A>
|
||||
std::ostream&
|
||||
fprint(std::ostream& os, boost::format& fmt, A&&... a) {
|
||||
apply_format(fmt, std::forward<A>(a)...);
|
||||
return os << fmt;
|
||||
}
|
||||
|
||||
template <typename... A>
|
||||
void
|
||||
print(boost::format& fmt, A&&... a) {
|
||||
fprint(std::cout, fmt, std::forward<A>(a)...);
|
||||
}
|
||||
|
||||
template <typename... A>
|
||||
std::ostream&
|
||||
fprint(std::ostream& os, const char* fmt, A&&... a) {
|
||||
boost::format bfmt(fmt);
|
||||
return fprint(os, bfmt, std::forward<A>(a)...);
|
||||
}
|
||||
|
||||
template <typename... A>
|
||||
void
|
||||
print(const char* fmt, A&&... a) {
|
||||
boost::format bfmt(fmt);
|
||||
return print(bfmt, std::forward<A>(a)...);
|
||||
}
|
||||
|
||||
template <typename... A>
|
||||
std::string
|
||||
sprint(const char* fmt, A&&... a) {
|
||||
boost::format bfmt(fmt);
|
||||
apply_format(bfmt, std::forward<A>(a)...);
|
||||
return bfmt.str();
|
||||
}
|
||||
|
||||
template <typename... A>
|
||||
std::string
|
||||
sprint(const sstring& fmt, A&&... a) {
|
||||
return sprint(fmt.c_str(), std::forward<A>(a)...);
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
std::string
|
||||
format_separated(Iterator b, Iterator e, const char* sep = ", ") {
|
||||
std::string ret;
|
||||
if (b == e) {
|
||||
return ret;
|
||||
}
|
||||
ret += *b++;
|
||||
while (b != e) {
|
||||
ret += sep;
|
||||
ret += *b++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename TimePoint>
|
||||
struct usecfmt_wrapper {
|
||||
TimePoint val;
|
||||
};
|
||||
|
||||
template <typename TimePoint>
|
||||
inline
|
||||
usecfmt_wrapper<TimePoint>
|
||||
usecfmt(TimePoint tp) {
|
||||
return { tp };
|
||||
};
|
||||
|
||||
template <typename Clock, typename Rep, typename Period>
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, usecfmt_wrapper<std::chrono::time_point<Clock, std::chrono::duration<Rep, Period>>> tp) {
|
||||
auto usec = std::chrono::duration_cast<std::chrono::microseconds>(tp.val.time_since_epoch()).count();
|
||||
std::ostream tmp(os.rdbuf());
|
||||
tmp << std::setw(12) << (usec / 1000000) << "." << std::setw(6) << std::setfill('0') << (usec % 1000000);
|
||||
return os;
|
||||
}
|
||||
|
||||
template <typename... A>
|
||||
void
|
||||
log(A&&... a) {
|
||||
std::cout << usecfmt(std::chrono::high_resolution_clock::now()) << " ";
|
||||
print(std::forward<A>(a)...);
|
||||
}
|
||||
|
||||
#endif /* PRINT_HH_ */
|
||||
220
core/queue.hh
220
core/queue.hh
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef QUEUE_HH_
|
||||
#define QUEUE_HH_
|
||||
|
||||
#include "circular_buffer.hh"
|
||||
#include "future.hh"
|
||||
#include <queue>
|
||||
#include <experimental/optional>
|
||||
|
||||
template <typename T>
|
||||
class queue {
|
||||
std::queue<T, circular_buffer<T>> _q;
|
||||
size_t _max;
|
||||
std::experimental::optional<promise<>> _not_empty;
|
||||
std::experimental::optional<promise<>> _not_full;
|
||||
private:
|
||||
void notify_not_empty();
|
||||
void notify_not_full();
|
||||
public:
|
||||
explicit queue(size_t size);
|
||||
|
||||
// Push an item.
|
||||
//
|
||||
// Returns false if the queue was full and the item was not pushed.
|
||||
bool push(T&& a);
|
||||
|
||||
// pops an item.
|
||||
T pop();
|
||||
|
||||
// Consumes items from the queue, passing them to @func, until @func
|
||||
// returns false or the queue it empty
|
||||
//
|
||||
// Returns false if func returned false.
|
||||
template <typename Func>
|
||||
bool consume(Func&& func);
|
||||
|
||||
// Returns true when the queue is empty.
|
||||
bool empty() const;
|
||||
|
||||
// Returns true when the queue is full.
|
||||
bool full() const;
|
||||
|
||||
// Returns a future<> that becomes available when pop() or consume()
|
||||
// can be called.
|
||||
future<> not_empty();
|
||||
|
||||
// Returns a future<> that becomes available when push() can be called.
|
||||
future<> not_full();
|
||||
|
||||
// Pops element now or when ther is some. Returns a future that becomes
|
||||
// available when some element is available.
|
||||
future<T> pop_eventually();
|
||||
|
||||
// Pushes the element now or when there is room. Returns a future<> which
|
||||
// resolves when data was pushed.
|
||||
future<> push_eventually(T&& data);
|
||||
|
||||
size_t size() const { return _q.size(); }
|
||||
|
||||
// Destroy any items in the queue, and pass the provided exception to any
|
||||
// waiting readers or writers.
|
||||
void abort(std::exception_ptr ex) {
|
||||
while (!_q.empty()) {
|
||||
_q.pop();
|
||||
}
|
||||
if (_not_full) {
|
||||
_not_full->set_exception(ex);
|
||||
_not_full= std::experimental::nullopt;
|
||||
}
|
||||
if (_not_empty) {
|
||||
_not_empty->set_exception(std::move(ex));
|
||||
_not_empty = std::experimental::nullopt;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
queue<T>::queue(size_t size)
|
||||
: _max(size) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
void queue<T>::notify_not_empty() {
|
||||
if (_not_empty) {
|
||||
_not_empty->set_value();
|
||||
_not_empty = std::experimental::optional<promise<>>();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
void queue<T>::notify_not_full() {
|
||||
if (_not_full) {
|
||||
_not_full->set_value();
|
||||
_not_full = std::experimental::optional<promise<>>();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool queue<T>::push(T&& data) {
|
||||
if (_q.size() < _max) {
|
||||
_q.push(std::move(data));
|
||||
notify_not_empty();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
T queue<T>::pop() {
|
||||
if (_q.size() == _max) {
|
||||
notify_not_full();
|
||||
}
|
||||
T data = std::move(_q.front());
|
||||
_q.pop();
|
||||
return data;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
future<T> queue<T>::pop_eventually() {
|
||||
if (empty()) {
|
||||
return not_empty().then([this] {
|
||||
return make_ready_future<T>(pop());
|
||||
});
|
||||
} else {
|
||||
return make_ready_future<T>(pop());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
future<> queue<T>::push_eventually(T&& data) {
|
||||
if (full()) {
|
||||
return not_full().then([this, data = std::move(data)] () mutable {
|
||||
_q.push(std::move(data));
|
||||
notify_not_empty();
|
||||
});
|
||||
} else {
|
||||
_q.push(std::move(data));
|
||||
notify_not_empty();
|
||||
return make_ready_future<>();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename Func>
|
||||
inline
|
||||
bool queue<T>::consume(Func&& func) {
|
||||
if (_q.size() == _max) {
|
||||
notify_not_full();
|
||||
}
|
||||
bool running = true;
|
||||
while (!_q.empty() && running) {
|
||||
running = func(std::move(_q.front()));
|
||||
_q.pop();
|
||||
}
|
||||
return running;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool queue<T>::empty() const {
|
||||
return _q.empty();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool queue<T>::full() const {
|
||||
return _q.size() == _max;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
future<> queue<T>::not_empty() {
|
||||
if (!empty()) {
|
||||
return make_ready_future<>();
|
||||
} else {
|
||||
_not_empty = promise<>();
|
||||
return _not_empty->get_future();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
future<> queue<T>::not_full() {
|
||||
if (!full()) {
|
||||
return make_ready_future<>();
|
||||
} else {
|
||||
_not_full = promise<>();
|
||||
return _not_full->get_future();
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* QUEUE_HH_ */
|
||||
140
core/ragel.hh
140
core/ragel.hh
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef RAGEL_HH_
|
||||
#define RAGEL_HH_
|
||||
|
||||
#include "sstring.hh"
|
||||
#include "temporary_buffer.hh"
|
||||
#include "util/eclipse.hh"
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <experimental/optional>
|
||||
#include "future.hh"
|
||||
|
||||
// Support classes for Ragel parsers
|
||||
|
||||
// Builds an sstring that can be scattered across multiple packets.
|
||||
//
|
||||
// Use a sstring_build::guard variable to designate each scattered
|
||||
// char array, and call mark_start() and mark_end() at the start
|
||||
// and end points, respectively. sstring_builder will collect data
|
||||
// from intervening segments, if needed.
|
||||
//
|
||||
// After mark_end() has been called, use the get() method to obtain
|
||||
// the built string.
|
||||
//
|
||||
// FIXME: switch to string_view.
|
||||
//
|
||||
class sstring_builder {
|
||||
sstring _value;
|
||||
const char* _start = nullptr;
|
||||
public:
|
||||
class guard;
|
||||
public:
|
||||
sstring get() && {
|
||||
return std::move(_value);
|
||||
}
|
||||
void reset() {
|
||||
_value.reset();
|
||||
_start = nullptr;
|
||||
}
|
||||
friend class guard;
|
||||
};
|
||||
|
||||
class sstring_builder::guard {
|
||||
sstring_builder& _builder;
|
||||
const char* _block_end;
|
||||
public:
|
||||
guard(sstring_builder& builder, const char* block_start, const char* block_end)
|
||||
: _builder(builder), _block_end(block_end) {
|
||||
if (!_builder._value.empty()) {
|
||||
mark_start(block_start);
|
||||
}
|
||||
}
|
||||
~guard() {
|
||||
if (_builder._start) {
|
||||
mark_end(_block_end);
|
||||
}
|
||||
}
|
||||
void mark_start(const char* p) {
|
||||
_builder._start = p;
|
||||
}
|
||||
void mark_end(const char* p) {
|
||||
if (_builder._value.empty()) {
|
||||
// avoid an allocation in the common case
|
||||
_builder._value = sstring(_builder._start, p);
|
||||
} else {
|
||||
_builder._value += sstring(_builder._start, p);
|
||||
}
|
||||
_builder._start = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// CRTP
|
||||
template <typename ConcreteParser>
|
||||
class ragel_parser_base {
|
||||
protected:
|
||||
int _fsm_cs;
|
||||
std::unique_ptr<int[]> _fsm_stack = nullptr;
|
||||
int _fsm_stack_size = 0;
|
||||
int _fsm_top;
|
||||
int _fsm_act;
|
||||
char* _fsm_ts;
|
||||
char* _fsm_te;
|
||||
sstring_builder _builder;
|
||||
protected:
|
||||
void init_base() {
|
||||
_builder.reset();
|
||||
}
|
||||
void prepush() {
|
||||
if (_fsm_top == _fsm_stack_size) {
|
||||
auto old = _fsm_stack_size;
|
||||
_fsm_stack_size = std::max(_fsm_stack_size * 2, 16);
|
||||
assert(_fsm_stack_size > old);
|
||||
std::unique_ptr<int[]> new_stack{new int[_fsm_stack_size]};
|
||||
std::copy(_fsm_stack.get(), _fsm_stack.get() + _fsm_top, new_stack.get());
|
||||
std::swap(_fsm_stack, new_stack);
|
||||
}
|
||||
}
|
||||
void postpop() {}
|
||||
sstring get_str() {
|
||||
auto s = std::move(_builder).get();
|
||||
return std::move(s);
|
||||
}
|
||||
public:
|
||||
using unconsumed_remainder = std::experimental::optional<temporary_buffer<char>>;
|
||||
future<unconsumed_remainder> operator()(temporary_buffer<char> buf) {
|
||||
char* p = buf.get_write();
|
||||
char* pe = p + buf.size();
|
||||
char* eof = buf.empty() ? pe : nullptr;
|
||||
char* parsed = static_cast<ConcreteParser*>(this)->parse(p, pe, eof);
|
||||
if (parsed) {
|
||||
buf.trim_front(parsed - p);
|
||||
return make_ready_future<unconsumed_remainder>(std::move(buf));
|
||||
}
|
||||
return make_ready_future<unconsumed_remainder>();
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* RAGEL_HH_ */
|
||||
2075
core/reactor.cc
2075
core/reactor.cc
File diff suppressed because it is too large
Load Diff
1369
core/reactor.hh
1369
core/reactor.hh
File diff suppressed because it is too large
Load Diff
191
core/resource.cc
191
core/resource.cc
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#include "resource.hh"
|
||||
#include "core/align.hh"
|
||||
|
||||
#ifdef HAVE_HWLOC
|
||||
|
||||
#include "util/defer.hh"
|
||||
#include <hwloc.h>
|
||||
#include <unordered_map>
|
||||
|
||||
cpu_set_t cpuid_to_cpuset(unsigned cpuid) {
|
||||
cpu_set_t cs;
|
||||
CPU_ZERO(&cs);
|
||||
CPU_SET(cpuid, &cs);
|
||||
return cs;
|
||||
}
|
||||
|
||||
namespace resource {
|
||||
|
||||
size_t div_roundup(size_t num, size_t denom) {
|
||||
return (num + denom - 1) / denom;
|
||||
}
|
||||
|
||||
static unsigned find_memory_depth(hwloc_topology_t& topology) {
|
||||
auto depth = hwloc_get_type_depth(topology, HWLOC_OBJ_PU);
|
||||
auto obj = hwloc_get_next_obj_by_depth(topology, depth, nullptr);
|
||||
|
||||
while (!obj->memory.local_memory && obj) {
|
||||
obj = hwloc_get_ancestor_obj_by_depth(topology, --depth, obj);
|
||||
}
|
||||
assert(obj);
|
||||
return depth;
|
||||
}
|
||||
|
||||
static size_t alloc_from_node(cpu& this_cpu, hwloc_obj_t node, std::unordered_map<hwloc_obj_t, size_t>& used_mem, size_t alloc) {
|
||||
auto taken = std::min(node->memory.local_memory - used_mem[node], alloc);
|
||||
if (taken) {
|
||||
used_mem[node] += taken;
|
||||
auto node_id = hwloc_bitmap_first(node->nodeset);
|
||||
assert(node_id != -1);
|
||||
this_cpu.mem.push_back({taken, unsigned(node_id)});
|
||||
}
|
||||
return taken;
|
||||
}
|
||||
|
||||
std::vector<cpu> allocate(configuration c) {
|
||||
hwloc_topology_t topology;
|
||||
hwloc_topology_init(&topology);
|
||||
auto free_hwloc = defer([&] { hwloc_topology_destroy(topology); });
|
||||
hwloc_topology_load(topology);
|
||||
if (c.cpu_set) {
|
||||
auto bm = hwloc_bitmap_alloc();
|
||||
auto free_bm = defer([&] { hwloc_bitmap_free(bm); });
|
||||
for (auto idx : *c.cpu_set) {
|
||||
hwloc_bitmap_set(bm, idx);
|
||||
}
|
||||
auto r = hwloc_topology_restrict(topology, bm,
|
||||
HWLOC_RESTRICT_FLAG_ADAPT_DISTANCES
|
||||
| HWLOC_RESTRICT_FLAG_ADAPT_MISC
|
||||
| HWLOC_RESTRICT_FLAG_ADAPT_IO);
|
||||
if (r == -1) {
|
||||
if (errno == ENOMEM) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
if (errno == EINVAL) {
|
||||
throw std::runtime_error("bad cpuset");
|
||||
}
|
||||
abort();
|
||||
}
|
||||
}
|
||||
auto machine_depth = hwloc_get_type_depth(topology, HWLOC_OBJ_MACHINE);
|
||||
assert(hwloc_get_nbobjs_by_depth(topology, machine_depth) == 1);
|
||||
auto machine = hwloc_get_obj_by_depth(topology, machine_depth, 0);
|
||||
auto available_memory = machine->memory.total_memory;
|
||||
available_memory -= c.reserve_memory.value_or(256 << 20);
|
||||
size_t mem = c.total_memory.value_or(available_memory);
|
||||
if (mem > available_memory) {
|
||||
throw std::runtime_error("insufficient physical memory");
|
||||
}
|
||||
unsigned available_procs = hwloc_get_nbobjs_by_type(topology, HWLOC_OBJ_PU);
|
||||
unsigned procs = c.cpus.value_or(available_procs);
|
||||
if (procs > available_procs) {
|
||||
throw std::runtime_error("insufficient processing units");
|
||||
}
|
||||
auto mem_per_proc = align_down<size_t>(mem / procs, 2 << 20);
|
||||
std::vector<hwloc_cpuset_t> cpu_sets{procs};
|
||||
auto root = hwloc_get_root_obj(topology);
|
||||
hwloc_distribute(topology, root, cpu_sets.data(), cpu_sets.size(), INT_MAX);
|
||||
std::vector<cpu> ret;
|
||||
std::unordered_map<hwloc_obj_t, size_t> topo_used_mem;
|
||||
std::vector<std::pair<cpu, size_t>> remains;
|
||||
size_t remain;
|
||||
unsigned depth = find_memory_depth(topology);
|
||||
|
||||
// Divide local memory to cpus
|
||||
for (auto&& cs : cpu_sets) {
|
||||
auto cpu_id = hwloc_bitmap_first(cs);
|
||||
assert(cpu_id != -1);
|
||||
auto pu = hwloc_get_pu_obj_by_os_index(topology, cpu_id);
|
||||
auto node = hwloc_get_ancestor_obj_by_depth(topology, depth, pu);
|
||||
cpu this_cpu;
|
||||
this_cpu.cpu_id = cpu_id;
|
||||
remain = mem_per_proc - alloc_from_node(this_cpu, node, topo_used_mem, mem_per_proc);
|
||||
|
||||
remains.emplace_back(std::move(this_cpu), remain);
|
||||
}
|
||||
|
||||
// Divide the rest of the memory
|
||||
for (auto&& r : remains) {
|
||||
cpu this_cpu;
|
||||
size_t remain;
|
||||
std::tie(this_cpu, remain) = r;
|
||||
auto pu = hwloc_get_pu_obj_by_os_index(topology, this_cpu.cpu_id);
|
||||
auto node = hwloc_get_ancestor_obj_by_depth(topology, depth, pu);
|
||||
auto obj = node;
|
||||
|
||||
while (remain) {
|
||||
remain -= alloc_from_node(this_cpu, obj, topo_used_mem, remain);
|
||||
do {
|
||||
obj = hwloc_get_next_obj_by_depth(topology, depth, obj);
|
||||
} while (!obj);
|
||||
if (obj == node)
|
||||
break;
|
||||
}
|
||||
assert(!remain);
|
||||
ret.push_back(std::move(this_cpu));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsigned nr_processing_units() {
|
||||
hwloc_topology_t topology;
|
||||
hwloc_topology_init(&topology);
|
||||
auto free_hwloc = defer([&] { hwloc_topology_destroy(topology); });
|
||||
hwloc_topology_load(topology);
|
||||
return hwloc_get_nbobjs_by_type(topology, HWLOC_OBJ_PU);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include "resource.hh"
|
||||
#include <unistd.h>
|
||||
|
||||
namespace resource {
|
||||
|
||||
std::vector<cpu> allocate(configuration c) {
|
||||
auto available_memory = ::sysconf(_SC_PAGESIZE) * size_t(::sysconf(_SC_PHYS_PAGES));
|
||||
available_memory -= c.reserve_memory.value_or(256 << 20);
|
||||
size_t mem = c.total_memory.value_or(available_memory);
|
||||
if (mem > available_memory) {
|
||||
throw std::runtime_error("insufficient physical memory");
|
||||
}
|
||||
auto cpuset_procs = c.cpu_set ? c.cpu_set->size() : nr_processing_units();
|
||||
auto procs = c.cpus.value_or(cpuset_procs);
|
||||
std::vector<cpu> ret;
|
||||
ret.reserve(procs);
|
||||
for (unsigned i = 0; i < procs; ++i) {
|
||||
ret.push_back(cpu{i, {{mem / procs, 0}}});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsigned nr_processing_units() {
|
||||
return ::sysconf(_SC_NPROCESSORS_ONLN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef RESOURCE_HH_
|
||||
#define RESOURCE_HH_
|
||||
|
||||
#include <cstdlib>
|
||||
#include <experimental/optional>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
cpu_set_t cpuid_to_cpuset(unsigned cpuid);
|
||||
|
||||
namespace resource {
|
||||
|
||||
using std::experimental::optional;
|
||||
|
||||
using cpuset = std::set<unsigned>;
|
||||
|
||||
struct configuration {
|
||||
optional<size_t> total_memory;
|
||||
optional<size_t> reserve_memory; // if total_memory not specified
|
||||
optional<size_t> cpus;
|
||||
optional<cpuset> cpu_set;
|
||||
};
|
||||
|
||||
struct memory {
|
||||
size_t bytes;
|
||||
unsigned nodeid;
|
||||
|
||||
};
|
||||
|
||||
struct cpu {
|
||||
unsigned cpu_id;
|
||||
std::vector<memory> mem;
|
||||
};
|
||||
|
||||
std::vector<cpu> allocate(configuration c);
|
||||
unsigned nr_processing_units();
|
||||
|
||||
}
|
||||
|
||||
#endif /* RESOURCE_HH_ */
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef CORE_RWLOCK_HH_
|
||||
#define CORE_RWLOCK_HH_
|
||||
|
||||
#include "semaphore.hh"
|
||||
|
||||
class rwlock {
|
||||
static const size_t max_ops = std::numeric_limits<size_t>::max();
|
||||
|
||||
semaphore _sem;
|
||||
public:
|
||||
rwlock()
|
||||
: _sem(max_ops) {
|
||||
}
|
||||
|
||||
future<> read_lock() {
|
||||
return _sem.wait();
|
||||
}
|
||||
void read_unlock() {
|
||||
_sem.signal();
|
||||
}
|
||||
future<> write_lock() {
|
||||
return _sem.wait(max_ops);
|
||||
}
|
||||
void write_unlock() {
|
||||
_sem.signal(max_ops);
|
||||
}
|
||||
bool try_read_lock() {
|
||||
return _sem.try_wait();
|
||||
}
|
||||
bool try_write_lock() {
|
||||
return _sem.try_wait(max_ops);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* CORE_RWLOCK_HH_ */
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef SCATTERED_MESSAGE_HH
|
||||
#define SCATTERED_MESSAGE_HH
|
||||
|
||||
#include "core/deleter.hh"
|
||||
#include "core/temporary_buffer.hh"
|
||||
#include "net/packet.hh"
|
||||
#include "sstring.hh"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <experimental/string_view>
|
||||
|
||||
template <typename CharType>
|
||||
class scattered_message {
|
||||
private:
|
||||
using fragment = net::fragment;
|
||||
using packet = net::packet;
|
||||
using char_type = CharType;
|
||||
packet _p;
|
||||
public:
|
||||
scattered_message() {}
|
||||
scattered_message(scattered_message&&) = default;
|
||||
scattered_message(const scattered_message&) = delete;
|
||||
|
||||
void append_static(const char_type* buf, size_t size) {
|
||||
if (size) {
|
||||
_p = packet(std::move(_p), fragment{(char_type*)buf, size}, deleter());
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void append_static(const char_type(&s)[N]) {
|
||||
append_static(s, N - 1);
|
||||
}
|
||||
|
||||
void append_static(const char_type* s) {
|
||||
append_static(s, strlen(s));
|
||||
}
|
||||
|
||||
template <typename size_type, size_type max_size>
|
||||
void append_static(const basic_sstring<char_type, size_type, max_size>& s) {
|
||||
append_static(s.begin(), s.size());
|
||||
}
|
||||
|
||||
void append_static(const std::experimental::string_view& s) {
|
||||
append_static(s.data(), s.size());
|
||||
}
|
||||
|
||||
template <typename size_type, size_type max_size>
|
||||
void append(basic_sstring<char_type, size_type, max_size> s) {
|
||||
if (s.size()) {
|
||||
_p = packet(std::move(_p), std::move(s).release());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename size_type, size_type max_size, typename Callback>
|
||||
void append(const basic_sstring<char_type, size_type, max_size>& s, Callback callback) {
|
||||
if (s.size()) {
|
||||
_p = packet(std::move(_p), fragment{s.begin(), s.size()}, make_deleter(std::move(callback)));
|
||||
}
|
||||
}
|
||||
|
||||
void reserve(int n_frags) {
|
||||
_p.reserve(n_frags);
|
||||
}
|
||||
|
||||
packet release() && {
|
||||
return std::move(_p);
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void on_delete(Callback callback) {
|
||||
_p = packet(std::move(_p), std::move(callback));
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return _p.len();
|
||||
}
|
||||
|
||||
size_t size() {
|
||||
return _p.len();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,524 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*
|
||||
* This work is open source software, licensed under the terms of the
|
||||
* BSD license as described in the LICENSE file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <forward_list>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "scollectd.hh"
|
||||
#include "core/future-util.hh"
|
||||
#include "core/reactor.hh"
|
||||
#include "net/api.hh"
|
||||
#include "scollectd_api.hh"
|
||||
|
||||
bool scollectd::type_instance_id::operator<(
|
||||
const scollectd::type_instance_id& id2) const {
|
||||
auto& id1 = *this;
|
||||
return std::tie(id1.plugin(), id1.plugin_instance(), id1.type(),
|
||||
id1.type_instance())
|
||||
< std::tie(id2.plugin(), id2.plugin_instance(), id2.type(),
|
||||
id2.type_instance());
|
||||
}
|
||||
bool scollectd::type_instance_id::operator==(
|
||||
const scollectd::type_instance_id & id2) const {
|
||||
auto& id1 = *this;
|
||||
return std::tie(id1.plugin(), id1.plugin_instance(), id1.type(),
|
||||
id1.type_instance())
|
||||
== std::tie(id2.plugin(), id2.plugin_instance(), id2.type(),
|
||||
id2.type_instance());
|
||||
}
|
||||
|
||||
namespace scollectd {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using clock_type = std::chrono::high_resolution_clock;
|
||||
|
||||
|
||||
static const ipv4_addr default_addr("239.192.74.66:25826");
|
||||
static const clock_type::duration default_period(1s);
|
||||
const plugin_instance_id per_cpu_plugin_instance("#cpu");
|
||||
|
||||
future<> send_metric(const type_instance_id &, const value_list &);
|
||||
|
||||
class impl {
|
||||
net::udp_channel _chan;
|
||||
timer<> _timer;
|
||||
|
||||
sstring _host = "localhost";
|
||||
ipv4_addr _addr = default_addr;
|
||||
clock_type::duration _period = default_period;
|
||||
uint64_t _num_packets = 0;
|
||||
uint64_t _millis = 0;
|
||||
uint64_t _bytes = 0;
|
||||
double _avg = 0;
|
||||
|
||||
public:
|
||||
typedef std::map<type_instance_id, shared_ptr<value_list> > value_list_map;
|
||||
typedef value_list_map::value_type value_list_pair;
|
||||
|
||||
void add_polled(const type_instance_id & id,
|
||||
const shared_ptr<value_list> & values) {
|
||||
_values[id] = values;
|
||||
}
|
||||
void remove_polled(const type_instance_id & id) {
|
||||
auto i = _values.find(id);
|
||||
if (i != _values.end()) {
|
||||
i->second = nullptr;
|
||||
}
|
||||
}
|
||||
// explicitly send a type_instance value list (outside polling)
|
||||
future<> send_metric(const type_instance_id & id,
|
||||
const value_list & values) {
|
||||
if (values.empty()) {
|
||||
return make_ready_future();
|
||||
}
|
||||
cpwriter out;
|
||||
out.put(_host, clock_type::duration(), id, values);
|
||||
return _chan.send(_addr, net::packet(out.data(), out.size()));
|
||||
}
|
||||
future<> send_notification(const type_instance_id & id,
|
||||
const sstring & msg) {
|
||||
cpwriter out;
|
||||
out.put(_host, id);
|
||||
out.put(part_type::Message, msg);
|
||||
return _chan.send(_addr, net::packet(out.data(), out.size()));
|
||||
}
|
||||
// initiates actual value polling -> send to target "loop"
|
||||
void start(const sstring & host, const ipv4_addr & addr,
|
||||
const clock_type::duration period) {
|
||||
_period = period;
|
||||
_addr = addr;
|
||||
_host = host;
|
||||
_chan = engine().net().make_udp_channel();
|
||||
_timer.set_callback(std::bind(&impl::run, this));
|
||||
|
||||
// dogfood ourselves
|
||||
_regs = {
|
||||
// total_bytes value:DERIVE:0:U
|
||||
add_polled_metric(
|
||||
type_instance_id("scollectd", per_cpu_plugin_instance,
|
||||
"total_bytes", "sent"),
|
||||
make_typed(data_type::DERIVE, _bytes)),
|
||||
// total_requests value:DERIVE:0:U
|
||||
add_polled_metric(
|
||||
type_instance_id("scollectd", per_cpu_plugin_instance,
|
||||
"total_requests"),
|
||||
make_typed(data_type::DERIVE, _num_packets)
|
||||
),
|
||||
// latency value:GAUGE:0:U
|
||||
add_polled_metric(
|
||||
type_instance_id("scollectd", per_cpu_plugin_instance,
|
||||
"latency"), _avg),
|
||||
// total_time_in_ms value:DERIVE:0:U
|
||||
add_polled_metric(
|
||||
type_instance_id("scollectd", per_cpu_plugin_instance,
|
||||
"total_time_in_ms"),
|
||||
make_typed(data_type::DERIVE, _millis)
|
||||
),
|
||||
// total_values value:DERIVE:0:U
|
||||
add_polled_metric(
|
||||
type_instance_id("scollectd", per_cpu_plugin_instance,
|
||||
"total_values"),
|
||||
make_typed(data_type::DERIVE, std::bind(&value_list_map::size, &_values))
|
||||
),
|
||||
// records value:GAUGE:0:U
|
||||
add_polled_metric(
|
||||
type_instance_id("scollectd", per_cpu_plugin_instance,
|
||||
"records"),
|
||||
make_typed(data_type::GAUGE, std::bind(&value_list_map::size, &_values))
|
||||
),
|
||||
};
|
||||
|
||||
send_notification(
|
||||
type_instance_id("scollectd", per_cpu_plugin_instance,
|
||||
"network"), "daemon started");
|
||||
arm();
|
||||
}
|
||||
void stop() {
|
||||
_timer.cancel();
|
||||
_regs.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
static const size_t payload_size = 1024;
|
||||
|
||||
void arm() {
|
||||
if (_period != clock_type::duration()) {
|
||||
_timer.arm(_period);
|
||||
}
|
||||
}
|
||||
|
||||
enum class part_type
|
||||
: uint16_t {
|
||||
Host = 0x0000, // The name of the host to associate with subsequent data values
|
||||
Time = 0x0001, // Time Numeric The timestamp to associate with subsequent data values, unix time format (seconds since epoch)
|
||||
TimeHr = 0x0008, // Time (high resolution) Numeric The timestamp to associate with subsequent data values. Time is defined in 2–30 seconds since epoch. New in Version 5.0.
|
||||
Plugin = 0x0002, // Plugin String The plugin name to associate with subsequent data values, e.g. "cpu"
|
||||
PluginInst = 0x0003, // Plugin instance String The plugin instance name to associate with subsequent data values, e.g. "1"
|
||||
Type = 0x0004, // Type String The type name to associate with subsequent data values, e.g. "cpu"
|
||||
TypeInst = 0x0005, // Type instance String The type instance name to associate with subsequent data values, e.g. "idle"
|
||||
Values = 0x0006, // Values other Data values, see above
|
||||
Interval = 0x0007, // Interval Numeric Interval used to set the "step" when creating new RRDs unless rrdtool plugin forces StepSize. Also used to detect values that have timed out.
|
||||
IntervalHr = 0x0009, // Interval (high resolution) Numeric The interval in which subsequent data values are collected. The interval is given in 2–30 seconds. New in Version 5.0.
|
||||
Message = 0x0100, // Message (notifications) String
|
||||
Severity = 0x0101, // Severity Numeric
|
||||
Signature = 0x0200, // Signature (HMAC-SHA-256) other (todo)
|
||||
Encryption = 0x0210, // Encryption (AES-256/OFB
|
||||
};
|
||||
|
||||
// "Time is defined in 2^–30 seconds since epoch. New in Version 5.0."
|
||||
typedef std::chrono::duration<uint64_t, std::ratio<1, 0x40000000>> collectd_hres_duration;
|
||||
|
||||
// yet another writer type, this one to construct collectd network
|
||||
// protocol data.
|
||||
struct cpwriter {
|
||||
typedef std::array<char, payload_size> buffer_type;
|
||||
typedef buffer_type::iterator mark_type;
|
||||
typedef buffer_type::const_iterator const_mark_type;
|
||||
|
||||
buffer_type _buf;
|
||||
mark_type _pos;
|
||||
bool _overflow = false;
|
||||
|
||||
std::unordered_map<uint16_t, sstring> _cache;
|
||||
|
||||
cpwriter()
|
||||
: _pos(_buf.begin()) {
|
||||
}
|
||||
mark_type mark() const {
|
||||
return _pos;
|
||||
}
|
||||
bool overflow() const {
|
||||
return _overflow;
|
||||
}
|
||||
void reset(mark_type m) {
|
||||
_pos = m;
|
||||
_overflow = false;
|
||||
}
|
||||
size_t size() const {
|
||||
return std::distance(_buf.begin(), const_mark_type(_pos));
|
||||
}
|
||||
bool empty() const {
|
||||
return _pos == _buf.begin();
|
||||
}
|
||||
void clear() {
|
||||
reset(_buf.begin());
|
||||
_cache.clear();
|
||||
_overflow = false;
|
||||
}
|
||||
const char * data() const {
|
||||
return &_buf.at(0);
|
||||
}
|
||||
char * data() {
|
||||
return &_buf.at(0);
|
||||
}
|
||||
cpwriter& check(size_t sz) {
|
||||
size_t av = std::distance(_pos, _buf.end());
|
||||
_overflow |= av < sz;
|
||||
return *this;
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return !_overflow;
|
||||
}
|
||||
bool operator!() const {
|
||||
return !operator bool();
|
||||
}
|
||||
template<typename _Iter>
|
||||
cpwriter & write(_Iter s, _Iter e) {
|
||||
if (check(std::distance(s, e))) {
|
||||
_pos = std::copy(s, e, _pos);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, cpwriter &>::type write(
|
||||
const T & t) {
|
||||
T tmp = net::hton(t);
|
||||
auto * p = reinterpret_cast<const uint8_t *>(&tmp);
|
||||
auto * e = p + sizeof(T);
|
||||
write(p, e);
|
||||
return *this;
|
||||
}
|
||||
cpwriter & write(const sstring & s) {
|
||||
write(s.begin(), s.end() + 1); // include \0
|
||||
return *this;
|
||||
}
|
||||
cpwriter & put(part_type type, const sstring & s) {
|
||||
write(uint16_t(type));
|
||||
write(uint16_t(4 + s.size() + 1)); // include \0
|
||||
write(s); // include \0
|
||||
return *this;
|
||||
}
|
||||
cpwriter & put_cached(part_type type, const sstring & s) {
|
||||
auto & cached = _cache[uint16_t(type)];
|
||||
if (cached != s) {
|
||||
put(type, s);
|
||||
cached = s;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, cpwriter &>::type put(
|
||||
part_type type, T t) {
|
||||
write(uint16_t(type));
|
||||
write(uint16_t(4 + sizeof(t)));
|
||||
write(t);
|
||||
return *this;
|
||||
}
|
||||
cpwriter & put(part_type type, const value_list & v) {
|
||||
auto s = v.size();
|
||||
auto sz = 6 + s + s * sizeof(uint64_t);
|
||||
if (check(sz)) {
|
||||
write(uint16_t(type));
|
||||
write(uint16_t(sz));
|
||||
write(uint16_t(s));
|
||||
v.types(reinterpret_cast<data_type *>(&(*_pos)));
|
||||
_pos += s;
|
||||
v.values(reinterpret_cast<net::packed<uint64_t> *>(&(*_pos)));
|
||||
_pos += s * sizeof(uint64_t);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
cpwriter & put(const sstring & host, const type_instance_id & id) {
|
||||
const auto ts = std::chrono::system_clock::now().time_since_epoch();
|
||||
const auto lrts =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(ts).count();
|
||||
|
||||
put_cached(part_type::Host, host);
|
||||
put(part_type::Time, uint64_t(lrts));
|
||||
// Seems hi-res timestamp does not work very well with
|
||||
// at the very least my default collectd in fedora (or I did it wrong?)
|
||||
// Use lo-res ts for now, it is probably quite sufficient.
|
||||
put_cached(part_type::Plugin, id.plugin());
|
||||
// Optional
|
||||
put_cached(part_type::PluginInst,
|
||||
id.plugin_instance() == per_cpu_plugin_instance ?
|
||||
to_sstring(engine().cpu_id()) : id.plugin_instance());
|
||||
put_cached(part_type::Type, id.type());
|
||||
// Optional
|
||||
put_cached(part_type::TypeInst, id.type_instance());
|
||||
return *this;
|
||||
}
|
||||
cpwriter & put(const sstring & host,
|
||||
const clock_type::duration & period,
|
||||
const type_instance_id & id, const value_list & v) {
|
||||
const auto ps = std::chrono::duration_cast<collectd_hres_duration>(
|
||||
period).count();
|
||||
put(host, id);
|
||||
put(part_type::Values, v);
|
||||
if (ps != 0) {
|
||||
put(part_type::IntervalHr, ps);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
void run() {
|
||||
typedef value_list_map::iterator iterator;
|
||||
typedef std::tuple<iterator, cpwriter> context;
|
||||
|
||||
auto ctxt = make_lw_shared<context>();
|
||||
|
||||
// note we're doing this unsynced since we assume
|
||||
// all registrations to this instance will be done on the
|
||||
// same cpu, and without interuptions (no wait-states)
|
||||
std::get<iterator>(*ctxt) = _values.begin();
|
||||
|
||||
auto stop_when = [this, ctxt]() {
|
||||
auto done = std::get<iterator>(*ctxt) == _values.end();
|
||||
return done;
|
||||
};
|
||||
// append as many values as we can fit into a packet (1024 bytes)
|
||||
auto send_packet = [this, ctxt]() {
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
auto & i = std::get<iterator>(*ctxt);
|
||||
auto & out = std::get<cpwriter>(*ctxt);
|
||||
|
||||
out.clear();
|
||||
|
||||
while (i != _values.end()) {
|
||||
// nullptr value list means removed value. so remove.
|
||||
if (!i->second) {
|
||||
i = _values.erase(i);
|
||||
continue;
|
||||
}
|
||||
auto m = out.mark();
|
||||
out.put(_host, _period, i->first, *i->second);
|
||||
if (!out) {
|
||||
out.reset(m);
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
if (out.empty()) {
|
||||
return make_ready_future();
|
||||
}
|
||||
return _chan.send(_addr, net::packet(out.data(), out.size())).then([start, ctxt, this]() {
|
||||
auto & out = std::get<cpwriter>(*ctxt);
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
// dogfood stats
|
||||
++_num_packets;
|
||||
_millis += std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count();
|
||||
_bytes += out.size();
|
||||
_avg = double(_millis) / _num_packets;
|
||||
}).then_wrapped([] (auto&& f) {
|
||||
try {
|
||||
f.get();
|
||||
} catch (std::exception & ex) {
|
||||
std::cout << "send failed: " << ex.what() << std::endl;
|
||||
} catch (...) {
|
||||
std::cout << "send failed: - unknown exception" << std::endl;
|
||||
}
|
||||
});
|
||||
};
|
||||
do_until(stop_when, send_packet).then([this]() {
|
||||
arm();
|
||||
});
|
||||
}
|
||||
public:
|
||||
shared_ptr<value_list> get_values(const type_instance_id & id) {
|
||||
return _values[id];
|
||||
}
|
||||
|
||||
std::vector<type_instance_id> get_instance_ids() {
|
||||
std::vector<type_instance_id> res;
|
||||
for (auto i: _values) {
|
||||
// Need to check for empty value_list, since unreg is two-stage.
|
||||
// Not an issue for most uses, but unit testing etc that would like
|
||||
// fully deterministic operation here would like us to only return
|
||||
// actually active ids
|
||||
if (i.second) {
|
||||
res.emplace_back(i.first);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
value_list_map _values;
|
||||
registrations _regs;
|
||||
};
|
||||
|
||||
impl & get_impl() {
|
||||
static thread_local impl per_cpu_instance;
|
||||
return per_cpu_instance;
|
||||
}
|
||||
|
||||
void add_polled(const type_instance_id & id,
|
||||
const shared_ptr<value_list> & values) {
|
||||
get_impl().add_polled(id, values);
|
||||
}
|
||||
|
||||
void remove_polled_metric(const type_instance_id & id) {
|
||||
get_impl().remove_polled(id);
|
||||
}
|
||||
|
||||
future<> send_notification(const type_instance_id & id,
|
||||
const sstring & msg) {
|
||||
return get_impl().send_notification(id, msg);
|
||||
}
|
||||
|
||||
future<> send_metric(const type_instance_id & id,
|
||||
const value_list & values) {
|
||||
return get_impl().send_metric(id, values);
|
||||
}
|
||||
|
||||
void configure(const boost::program_options::variables_map & opts) {
|
||||
bool enable = opts["collectd"].as<bool>();
|
||||
if (!enable) {
|
||||
return;
|
||||
}
|
||||
auto addr = ipv4_addr(opts["collectd-address"].as<std::string>());
|
||||
auto period = std::chrono::duration_cast<clock_type::duration>(
|
||||
std::chrono::milliseconds(
|
||||
opts["collectd-poll-period"].as<unsigned>()));
|
||||
|
||||
auto host = opts["collectd-hostname"].as<std::string>();
|
||||
|
||||
// Now create send loops on each cpu
|
||||
for (unsigned c = 0; c < smp::count; c++) {
|
||||
smp::submit_to(c, [=] () {
|
||||
get_impl().start(host, addr, period);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
boost::program_options::options_description get_options_description() {
|
||||
namespace bpo = boost::program_options;
|
||||
bpo::options_description opts("COLLECTD options");
|
||||
char hostname[PATH_MAX];
|
||||
gethostname(hostname, sizeof(hostname));
|
||||
hostname[PATH_MAX-1] = '\0';
|
||||
opts.add_options()("collectd", bpo::value<bool>()->default_value(true),
|
||||
"enable collectd daemon")("collectd-address",
|
||||
bpo::value<std::string>()->default_value("239.192.74.66:25826"),
|
||||
"address to send/broadcast metrics to")("collectd-poll-period",
|
||||
bpo::value<unsigned>()->default_value(1000),
|
||||
"poll period - frequency of sending counter metrics (default: 1000ms, 0 disables)")(
|
||||
"collectd-hostname",
|
||||
bpo::value<std::string>()->default_value(hostname),
|
||||
"collectd host name");
|
||||
return opts;
|
||||
}
|
||||
|
||||
std::vector<collectd_value> get_collectd_value(
|
||||
const scollectd::type_instance_id& id) {
|
||||
std::vector<collectd_value> res_values;
|
||||
auto raw_types = get_impl().get_values(id);
|
||||
if (raw_types == nullptr) {
|
||||
return res_values;
|
||||
}
|
||||
std::vector<data_type> types(raw_types->size());
|
||||
raw_types->types(&(*types.begin()));
|
||||
std::vector<net::packed<uint64_t>> value(raw_types->size());
|
||||
raw_types->values(&(*value.begin()));
|
||||
|
||||
auto i = value.begin();
|
||||
for (auto t : types) {
|
||||
collectd_value c(t, be64toh(*i));
|
||||
res_values.push_back(c);
|
||||
}
|
||||
return res_values;
|
||||
}
|
||||
|
||||
std::vector<data_type> get_collectd_types(
|
||||
const scollectd::type_instance_id& id) {
|
||||
auto res = get_impl().get_values(id);
|
||||
if (res == nullptr) {
|
||||
return std::vector<data_type>();
|
||||
}
|
||||
std::vector<data_type> vals(res->size());
|
||||
res->types(&(*vals.begin()));
|
||||
return vals;
|
||||
}
|
||||
|
||||
std::vector<scollectd::type_instance_id> get_collectd_ids() {
|
||||
return get_impl().get_instance_ids();
|
||||
}
|
||||
}
|
||||
@@ -1,471 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*
|
||||
* This work is open source software, licensed under the terms of the
|
||||
* BSD license as described in the LICENSE file in the top-level directory.
|
||||
*/
|
||||
|
||||
#ifndef SCOLLECTD_HH_
|
||||
#define SCOLLECTD_HH_
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <chrono>
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include "future.hh"
|
||||
#include "net/byteorder.hh"
|
||||
#include "core/shared_ptr.hh"
|
||||
#include "core/sstring.hh"
|
||||
|
||||
/**
|
||||
* Implementation of rudimentary collectd data gathering.
|
||||
*
|
||||
* Usage is hopefully straight forward. Though, feel free to read
|
||||
* https://collectd.org/wiki/index.php/Naming_schema
|
||||
* for an explanation on the naming model.
|
||||
*
|
||||
* Typically, you'll add values something like:
|
||||
*
|
||||
* scollectd::type_instance_id typ("<pluginname>", "<instance_name>", "<type_name>", "<instance_name>");
|
||||
* scollectd::add_polled_metric(typ, [<metric var> | scollectd::make_typed(<data_type>, <metric_var>) [, ...]);
|
||||
*
|
||||
* Where
|
||||
* <pluginname> would be the overall 'module', e.g. "cpu"
|
||||
* <instance_name> -> optional distinguisher between plugin instances. For cpu, the built-in
|
||||
* scollectd::per_cpu_plugin_instance constant is a good choice, i.e. 0->N cpu.
|
||||
* If there are no instances (e.g. only one), empty constant is appropriate (none)
|
||||
* <type_name> is the 'type' of metric collected, for ex. "usage" (cpu/0/usage)
|
||||
* <type_instance> is a distinguisher for metric parts of the type, e.g. "idle", "user", "kernel"
|
||||
* -> cpu/0/usage/idle | cpu/0/usage/user | cpu/0/usage/kernel
|
||||
*
|
||||
* Each type instance can bind an arbitrary number of values, ech representing some aspect in turn of the instance.
|
||||
* The structure and interpretation is up to the producer/consumer
|
||||
*
|
||||
* There is a single "scollectd" instance per cpu, and values should be bound locally
|
||||
* to this cpu. Polling is done at a frequency set in the seastar config (def once per s),
|
||||
* and all registered values will be sent via UDP packages to the destination host(s)
|
||||
*
|
||||
* Note that the tuple { plugin, plugin_instance, type, type_instance } is considered a
|
||||
* unique ID for a value registration, so using the same tuple twice will remove the previously
|
||||
* registered values.
|
||||
*
|
||||
* Values can be unregistered at any time, though they must be so on the same thread/cpu
|
||||
* as they we're registered. The "registration" achor type provides RAII style value unregistration
|
||||
* semantics.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace scollectd {
|
||||
|
||||
// The value binding data types
|
||||
enum class data_type : uint8_t {
|
||||
COUNTER, // unsigned int 64
|
||||
GAUGE, // double
|
||||
DERIVE, // signed int 64
|
||||
ABSOLUTE, // unsigned int 64
|
||||
};
|
||||
|
||||
// don't use directly. use make_typed.
|
||||
template<typename T>
|
||||
struct typed {
|
||||
typed(data_type t, T && v)
|
||||
: type(t), value(std::forward<T>(v)) {
|
||||
}
|
||||
data_type type;
|
||||
T value;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
static inline typed<T> make_typed(data_type type, T&& t) {
|
||||
return typed<T>(type, std::forward<T>(t));
|
||||
}
|
||||
|
||||
typedef sstring plugin_id;
|
||||
typedef sstring plugin_instance_id;
|
||||
typedef sstring type_id;
|
||||
typedef sstring type_instance;
|
||||
|
||||
class type_instance_id {
|
||||
public:
|
||||
type_instance_id() = default;
|
||||
type_instance_id(const plugin_id & p, const plugin_instance_id & pi,
|
||||
const type_id & t, const scollectd::type_instance & ti = std::string())
|
||||
: _plugin(p), _plugin_instance(pi), _type(t), _type_instance(ti) {
|
||||
}
|
||||
type_instance_id(type_instance_id &&) = default;
|
||||
type_instance_id(const type_instance_id &) = default;
|
||||
|
||||
type_instance_id & operator=(type_instance_id &&) = default;
|
||||
type_instance_id & operator=(const type_instance_id &) = default;
|
||||
|
||||
const plugin_id & plugin() const {
|
||||
return _plugin;
|
||||
}
|
||||
const plugin_instance_id & plugin_instance() const {
|
||||
return _plugin_instance;
|
||||
}
|
||||
const type_id & type() const {
|
||||
return _type;
|
||||
}
|
||||
const scollectd::type_instance & type_instance() const {
|
||||
return _type_instance;
|
||||
}
|
||||
bool operator<(const type_instance_id&) const;
|
||||
bool operator==(const type_instance_id&) const;
|
||||
private:
|
||||
plugin_id _plugin;
|
||||
plugin_instance_id _plugin_instance;
|
||||
type_id _type;
|
||||
scollectd::type_instance _type_instance;
|
||||
};
|
||||
|
||||
extern const plugin_instance_id per_cpu_plugin_instance;
|
||||
|
||||
void configure(const boost::program_options::variables_map&);
|
||||
boost::program_options::options_description get_options_description();
|
||||
void remove_polled_metric(const type_instance_id &);
|
||||
|
||||
/**
|
||||
* Anchor for polled registration.
|
||||
* Iff the registered type is in some way none-persistent,
|
||||
* use this as receiver of the reg and ensure it dies before the
|
||||
* added value(s).
|
||||
*
|
||||
* Use:
|
||||
* uint64_t v = 0;
|
||||
* registration r = add_polled_metric(v);
|
||||
* ++r;
|
||||
* <scope end, above dies>
|
||||
*/
|
||||
struct registration {
|
||||
registration() = default;
|
||||
registration(const type_instance_id& id)
|
||||
: _id(id) {
|
||||
}
|
||||
registration(type_instance_id&& id)
|
||||
: _id(std::move(id)) {
|
||||
}
|
||||
registration(const registration&) = delete;
|
||||
registration(registration&&) = default;
|
||||
~registration() {
|
||||
unregister();
|
||||
}
|
||||
registration & operator=(const registration&) = delete;
|
||||
registration & operator=(registration&&) = default;
|
||||
|
||||
void unregister() {
|
||||
remove_polled_metric(_id);
|
||||
_id = type_instance_id();
|
||||
}
|
||||
private:
|
||||
type_instance_id _id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper type to make generating vectors of registration objects
|
||||
* easier, since it constructs from an initializer list of
|
||||
* type_instance_id:s, avoiding early conversion to registration objs,
|
||||
* which in case of init lists, are copy semantics, not move...
|
||||
*/
|
||||
class registrations
|
||||
: public std::vector<registration>
|
||||
{
|
||||
public:
|
||||
typedef std::vector<registration> vector_type;
|
||||
|
||||
registrations()
|
||||
{}
|
||||
registrations(vector_type&& v) : vector_type(std::move(v))
|
||||
{}
|
||||
registrations(const std::initializer_list<type_instance_id>& l)
|
||||
: vector_type(l.begin(),l.end())
|
||||
{}
|
||||
registrations& operator=(vector_type&& v) {
|
||||
vector_type::operator=(std::move(v));
|
||||
return *this;
|
||||
}
|
||||
registrations& operator=(const std::initializer_list<type_instance_id>& l) {
|
||||
return registrations::operator=(registrations(l));
|
||||
}
|
||||
};
|
||||
|
||||
// lots of template junk to build typed value list tuples
|
||||
// for registered values.
|
||||
template<typename T, typename En = void>
|
||||
struct data_type_for;
|
||||
|
||||
template<typename T, typename En = void>
|
||||
struct is_callable;
|
||||
|
||||
template<typename T>
|
||||
struct is_callable<T,
|
||||
typename std::enable_if<
|
||||
!std::is_void<typename std::result_of<T()>::type>::value,
|
||||
void>::type> : public std::true_type {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct is_callable<T,
|
||||
typename std::enable_if<std::is_fundamental<T>::value, void>::type> : public std::false_type {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct data_type_for<T,
|
||||
typename std::enable_if<
|
||||
std::is_integral<T>::value && std::is_unsigned<T>::value,
|
||||
void>::type> : public std::integral_constant<data_type,
|
||||
data_type::COUNTER> {
|
||||
};
|
||||
template<typename T>
|
||||
struct data_type_for<T,
|
||||
typename std::enable_if<
|
||||
std::is_integral<T>::value && std::is_signed<T>::value, void>::type> : public std::integral_constant<
|
||||
data_type, data_type::DERIVE> {
|
||||
};
|
||||
template<typename T>
|
||||
struct data_type_for<T,
|
||||
typename std::enable_if<std::is_floating_point<T>::value, void>::type> : public std::integral_constant<
|
||||
data_type, data_type::GAUGE> {
|
||||
};
|
||||
template<typename T>
|
||||
struct data_type_for<T,
|
||||
typename std::enable_if<is_callable<T>::value, void>::type> : public data_type_for<
|
||||
typename std::result_of<T()>::type> {
|
||||
};
|
||||
template<typename T>
|
||||
struct data_type_for<typed<T>> : public data_type_for<T> {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class value {
|
||||
public:
|
||||
template<typename W>
|
||||
struct wrap {
|
||||
wrap(const W & v)
|
||||
: _v(v) {
|
||||
}
|
||||
const W & operator()() const {
|
||||
return _v;
|
||||
}
|
||||
const W & _v;
|
||||
};
|
||||
|
||||
typedef typename std::remove_reference<T>::type value_type;
|
||||
typedef typename std::conditional<
|
||||
is_callable<typename std::remove_reference<T>::type>::value,
|
||||
value_type, wrap<value_type> >::type stored_type;
|
||||
|
||||
value(const value_type & t)
|
||||
: value<T>(data_type_for<value_type>::value, t) {
|
||||
}
|
||||
value(data_type type, const value_type & t)
|
||||
: _type(type), _t(t) {
|
||||
}
|
||||
uint64_t operator()() const {
|
||||
auto v = _t();
|
||||
if (_type == data_type::GAUGE) {
|
||||
return convert(double(v));
|
||||
} else {
|
||||
uint64_t u = v;
|
||||
return convert(u);
|
||||
}
|
||||
}
|
||||
operator uint64_t() const {
|
||||
return (*this)();
|
||||
}
|
||||
operator data_type() const {
|
||||
return _type;
|
||||
}
|
||||
data_type type() const {
|
||||
return _type;
|
||||
}
|
||||
private:
|
||||
// not super quick value -> protocol endian 64-bit values.
|
||||
template<typename _Iter>
|
||||
void bpack(_Iter s, _Iter e, uint64_t v) const {
|
||||
while (s != e) {
|
||||
*s++ = (v & 0xff);
|
||||
v >>= 8;
|
||||
}
|
||||
}
|
||||
template<typename V>
|
||||
typename std::enable_if<std::is_integral<V>::value, uint64_t>::type convert(
|
||||
V v) const {
|
||||
uint64_t i = v;
|
||||
// network byte order
|
||||
return ntohq(i);
|
||||
}
|
||||
template<typename V>
|
||||
typename std::enable_if<std::is_floating_point<V>::value, uint64_t>::type convert(
|
||||
V t) const {
|
||||
union {
|
||||
uint64_t i;
|
||||
double v;
|
||||
} v;
|
||||
union {
|
||||
uint64_t i;
|
||||
uint8_t b[8];
|
||||
} u;
|
||||
v.v = t;
|
||||
// intel byte order. could also obviously be faster.
|
||||
// could be ignored if we just assume we're le (for now),
|
||||
// but this is ok me thinks.
|
||||
bpack(std::begin(u.b), std::end(u.b), v.i);
|
||||
return u.i;
|
||||
}
|
||||
;
|
||||
|
||||
const data_type _type;
|
||||
const stored_type _t;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class value<typed<T>> : public value<T> {
|
||||
public:
|
||||
value(const typed<T> & args)
|
||||
: value<T>(args.type, args.value) {
|
||||
}
|
||||
};
|
||||
|
||||
class value_list {
|
||||
public:
|
||||
virtual ~value_list() {}
|
||||
virtual size_t size() const = 0;
|
||||
|
||||
virtual void types(data_type *) const = 0;
|
||||
virtual void values(net::packed<uint64_t> *) const = 0;
|
||||
|
||||
bool empty() const {
|
||||
return size() == 0;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename ... Args>
|
||||
class values_impl: public value_list {
|
||||
public:
|
||||
static const size_t num_values = sizeof...(Args);
|
||||
|
||||
values_impl(Args&& ...args)
|
||||
: _values(std::forward<Args>(args)...)
|
||||
{}
|
||||
|
||||
values_impl(values_impl<Args...>&& a) = default;
|
||||
values_impl(const values_impl<Args...>& a) = default;
|
||||
|
||||
size_t size() const override {
|
||||
return num_values;
|
||||
}
|
||||
void types(data_type * p) const override {
|
||||
unpack(_values, [p](Args... args) {
|
||||
std::initializer_list<data_type> tmp = { args... };
|
||||
std::copy(tmp.begin(), tmp.end(), p);
|
||||
});
|
||||
}
|
||||
void values(net::packed<uint64_t> * p) const override {
|
||||
unpack(_values, [p](Args... args) {
|
||||
std::initializer_list<uint64_t> tmp = { args... };
|
||||
std::copy(tmp.begin(), tmp.end(), p);
|
||||
});
|
||||
}
|
||||
private:
|
||||
template<typename _Op>
|
||||
void unpack(const std::tuple<Args...>& t, _Op&& op) const {
|
||||
do_unpack(t, std::index_sequence_for<Args...> {}, std::forward<_Op>(op));
|
||||
}
|
||||
|
||||
template<size_t ...S, typename _Op>
|
||||
void do_unpack(const std::tuple<Args...>& t, const std::index_sequence<S...> &, _Op&& op) const {
|
||||
op(std::get<S>(t)...);
|
||||
}
|
||||
|
||||
std::tuple < Args... > _values;
|
||||
};
|
||||
|
||||
void add_polled(const type_instance_id &, const shared_ptr<value_list> &);
|
||||
|
||||
typedef std::function<void()> notify_function;
|
||||
template<typename... _Args>
|
||||
static auto make_type_instance(_Args && ... args) -> values_impl < decltype(value<_Args>(std::forward<_Args>(args)))... >
|
||||
{
|
||||
return values_impl<decltype(value<_Args>(std::forward<_Args>(args)))... >
|
||||
(value<_Args>(std::forward<_Args>(args))...);
|
||||
}
|
||||
template<typename ... _Args>
|
||||
static type_instance_id add_polled_metric(const plugin_id & plugin,
|
||||
const plugin_instance_id & plugin_instance, const type_id & type,
|
||||
const scollectd::type_instance & type_instance, _Args&& ... args) {
|
||||
return add_polled_metric(
|
||||
type_instance_id(plugin, plugin_instance, type, type_instance),
|
||||
std::forward<_Args>(args)...);
|
||||
}
|
||||
template<typename ... _Args>
|
||||
static future<> send_explicit_metric(const plugin_id & plugin,
|
||||
const plugin_instance_id & plugin_instance, const type_id & type,
|
||||
const scollectd::type_instance & type_instance, _Args&& ... args) {
|
||||
return send_explicit_metric(
|
||||
type_instance_id(plugin, plugin_instance, type, type_instance),
|
||||
std::forward<_Args>(args)...);
|
||||
}
|
||||
template<typename ... _Args>
|
||||
static notify_function create_explicit_metric(const plugin_id & plugin,
|
||||
const plugin_instance_id & plugin_instance, const type_id & type,
|
||||
const scollectd::type_instance & type_instance, _Args&& ... args) {
|
||||
return create_explicit_metric(
|
||||
type_instance_id(plugin, plugin_instance, type, type_instance),
|
||||
std::forward<_Args>(args)...);
|
||||
}
|
||||
template<typename ... _Args>
|
||||
static type_instance_id add_polled_metric(const type_instance_id & id,
|
||||
_Args&& ... args) {
|
||||
typedef decltype(make_type_instance(std::forward<_Args>(args)...)) impl_type;
|
||||
add_polled(id,
|
||||
::make_shared<impl_type>(
|
||||
make_type_instance(std::forward<_Args>(args)...)));
|
||||
return id;
|
||||
}
|
||||
// "Explicit" metric sends. Sends a single value list as a message.
|
||||
// Obviously not super efficient either. But maybe someone needs it sometime.
|
||||
template<typename ... _Args>
|
||||
static future<> send_explicit_metric(const type_instance_id & id,
|
||||
_Args&& ... args) {
|
||||
return send_metric(id, make_type_instance(std::forward<_Args>(args)...));
|
||||
}
|
||||
template<typename ... _Args>
|
||||
static notify_function create_explicit_metric(const type_instance_id & id,
|
||||
_Args&& ... args) {
|
||||
auto list = make_type_instance(std::forward<_Args>(args)...);
|
||||
return [id, list=std::move(list)]() {
|
||||
send_metric(id, list);
|
||||
};
|
||||
}
|
||||
|
||||
// Send a message packet (string)
|
||||
future<> send_notification(const type_instance_id & id, const sstring & msg);
|
||||
};
|
||||
|
||||
#endif /* SCOLLECTD_HH_ */
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef CORE_SCOLLECTD_API_HH_
|
||||
#define CORE_SCOLLECTD_API_HH_
|
||||
|
||||
#include "core/scollectd.hh"
|
||||
|
||||
namespace scollectd {
|
||||
|
||||
struct collectd_value {
|
||||
union {
|
||||
double _d;
|
||||
uint64_t _ui;
|
||||
int64_t _i;
|
||||
} u;
|
||||
scollectd::data_type _type;
|
||||
collectd_value()
|
||||
: _type(data_type::GAUGE) {
|
||||
}
|
||||
collectd_value(data_type t, uint64_t i)
|
||||
: _type(t) {
|
||||
u._ui = i;
|
||||
}
|
||||
|
||||
collectd_value& operator=(const collectd_value& c) = default;
|
||||
|
||||
collectd_value& operator+=(const collectd_value& c) {
|
||||
*this = *this + c;
|
||||
return *this;
|
||||
}
|
||||
|
||||
collectd_value operator+(const collectd_value& c) {
|
||||
collectd_value res(*this);
|
||||
switch (_type) {
|
||||
case data_type::GAUGE:
|
||||
res.u._d += c.u._d;
|
||||
break;
|
||||
case data_type::DERIVE:
|
||||
res.u._i += c.u._i;
|
||||
break;
|
||||
default:
|
||||
res.u._ui += c.u._ui;
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<collectd_value> get_collectd_value(
|
||||
const scollectd::type_instance_id& id);
|
||||
|
||||
std::vector<scollectd::type_instance_id> get_collectd_ids();
|
||||
|
||||
}
|
||||
|
||||
#endif /* CORE_SCOLLECTD_API_HH_ */
|
||||
202
core/seastar.hh
202
core/seastar.hh
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/// \mainpage
|
||||
///
|
||||
/// Seastar is a high performance C++ application framework for high
|
||||
/// concurrency server applications.
|
||||
///
|
||||
/// Please see:
|
||||
/// - \ref future-module Documentation on futures and promises, which are
|
||||
/// the seastar building blocks.
|
||||
/// - \ref future-util Utililty functions for working with futures
|
||||
/// - \ref memory-module Memory management
|
||||
/// - \ref networking-module TCP/IP networking
|
||||
/// - \ref fileio-module File Input/Output
|
||||
/// - \ref smp-module Multicore support
|
||||
/// - \ref fiber-module Utilities for managing loosely coupled chains of
|
||||
/// continuations, also known as fibers
|
||||
/// - \ref thread-module Support for traditional threaded execution
|
||||
|
||||
#include "sstring.hh"
|
||||
#include "future.hh"
|
||||
|
||||
// iostream.hh
|
||||
template <class CharType> class input_stream;
|
||||
template <class CharType> class output_stream;
|
||||
|
||||
// reactor.hh
|
||||
class server_socket;
|
||||
class connected_socket;
|
||||
class socket_address;
|
||||
class listen_options;
|
||||
|
||||
// file.hh
|
||||
class file;
|
||||
enum class open_flags;
|
||||
|
||||
// Networking API
|
||||
|
||||
/// \defgroup networking-module Networking
|
||||
///
|
||||
/// Seastar provides a simple networking API, backed by two
|
||||
/// TCP/IP stacks: the POSIX stack, utilizing the kernel's
|
||||
/// BSD socket APIs, and the native stack, implement fully
|
||||
/// within seastar and able to drive network cards directly.
|
||||
/// The native stack supports zero-copy on both transmit
|
||||
/// and receive, and is implemented using seastar's high
|
||||
/// performance, lockless sharded design. The network stack
|
||||
/// can be selected with the \c \--network-stack command-line
|
||||
/// parameter.
|
||||
|
||||
/// \addtogroup networking-module
|
||||
/// @{
|
||||
|
||||
/// Listen for connections on a given port
|
||||
///
|
||||
/// Starts listening on a given address for incoming connections.
|
||||
///
|
||||
/// \param sa socket address to listen on
|
||||
///
|
||||
/// \return \ref server_socket object ready to accept connections.
|
||||
///
|
||||
/// \see listen(socket_address sa, listen_options opts)
|
||||
server_socket listen(socket_address sa);
|
||||
|
||||
/// Listen for connections on a given port
|
||||
///
|
||||
/// Starts listening on a given address for incoming connections.
|
||||
///
|
||||
/// \param sa socket address to listen on
|
||||
/// \param opts options controlling the listen operation
|
||||
///
|
||||
/// \return \ref server_socket object ready to accept connections.
|
||||
///
|
||||
/// \see listen(socket_address sa)
|
||||
server_socket listen(socket_address sa, listen_options opts);
|
||||
|
||||
/// Establishes a connection to a given address
|
||||
///
|
||||
/// Attempts to connect to the given address.
|
||||
///
|
||||
/// \param sa socket address to connect to
|
||||
///
|
||||
/// \return a \ref connected_socket object, or an exception
|
||||
future<connected_socket> connect(socket_address sa);
|
||||
|
||||
/// @}
|
||||
|
||||
/// \defgroup fileio-module File Input/Output
|
||||
///
|
||||
/// Seastar provides a file API to deal with persistent storage.
|
||||
/// Unlike most file APIs, seastar offers unbuffered file I/O
|
||||
/// (similar to, and based on, \c O_DIRECT). Unbuffered I/O means
|
||||
/// that the application is required to do its own caching, but
|
||||
/// delivers better performance if this caching is done correctly.
|
||||
///
|
||||
/// For random I/O or sequential unbuffered I/O, the \ref file
|
||||
/// class provides a set of methods for reading, writing, discarding,
|
||||
/// or otherwise manipulating a file. For buffered sequential I/O,
|
||||
/// see \ref make_file_input_stream() and \ref make_file_output_stream().
|
||||
|
||||
/// \addtogroup fileio-module
|
||||
/// @{
|
||||
|
||||
/// Opens or creates a file. The "dma" in the name refers to the fact
|
||||
/// that data transfers are unbuffered and uncached.
|
||||
///
|
||||
/// \param name the name of the file to open or create
|
||||
/// \param flags various flags controlling the open process
|
||||
/// \return a \ref file object, as a future
|
||||
///
|
||||
/// \note
|
||||
/// The file name is not guaranteed to be stable on disk, unless the
|
||||
/// containing directory is sync'ed.
|
||||
///
|
||||
/// \relates file
|
||||
future<file> open_file_dma(sstring name, open_flags flags);
|
||||
|
||||
/// Opens a directory.
|
||||
///
|
||||
/// \param name name of the directory to open
|
||||
///
|
||||
/// \return a \ref file object representing a directory. The only
|
||||
/// legal operations are \ref file::list_directory(),
|
||||
/// \ref file::fsync(), and \ref file::close().
|
||||
///
|
||||
/// \relates file
|
||||
future<file> open_directory(sstring name);
|
||||
|
||||
/// Creates a new directory.
|
||||
///
|
||||
/// \param name name of the directory to create
|
||||
///
|
||||
/// \note
|
||||
/// The directory is not guaranteed to be stable on disk, unless the
|
||||
/// containing directory is sync'ed.
|
||||
future<> make_directory(sstring name);
|
||||
|
||||
/// Ensures a directory exists
|
||||
///
|
||||
/// Checks whether a directory exists, and if not, creates it. Only
|
||||
/// the last component of the directory name is created.
|
||||
///
|
||||
/// \param name name of the directory to potentially create
|
||||
///
|
||||
/// \note
|
||||
/// The directory is not guaranteed to be stable on disk, unless the
|
||||
/// containing directory is sync'ed.
|
||||
future<> touch_directory(sstring name);
|
||||
|
||||
/// Recursively ensures a directory exists
|
||||
///
|
||||
/// Checks whether each component of a directory exists, and if not, creates it.
|
||||
///
|
||||
/// \param name name of the directory to potentially create
|
||||
/// \param separator character used as directory separator
|
||||
///
|
||||
/// \note
|
||||
/// This function fsyncs each component created, and is therefore guaranteed to be stable on disk.
|
||||
future<> recursive_touch_directory(sstring name);
|
||||
|
||||
/// Removes (unlinks) a file.
|
||||
///
|
||||
/// \param name name of the file to remove
|
||||
///
|
||||
/// \note
|
||||
/// The removal is not guaranteed to be stable on disk, unless the
|
||||
/// containing directory is sync'ed.
|
||||
future<> remove_file(sstring name);
|
||||
|
||||
/// Renames (moves) a file.
|
||||
///
|
||||
/// \param old_name existing file name
|
||||
/// \param new_name new file name
|
||||
///
|
||||
/// \note
|
||||
/// The rename is not guaranteed to be stable on disk, unless the
|
||||
/// both containing directories are sync'ed.
|
||||
future<> rename_file(sstring old_name, sstring new_name);
|
||||
|
||||
/// @}
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef CORE_SEMAPHORE_HH_
|
||||
#define CORE_SEMAPHORE_HH_
|
||||
|
||||
#include "future.hh"
|
||||
#include <list>
|
||||
#include <stdexcept>
|
||||
#include <exception>
|
||||
#include "timer.hh"
|
||||
|
||||
/// \addtogroup fiber-module
|
||||
/// @{
|
||||
|
||||
/// Exception thrown when a semaphore is broken by
|
||||
/// \ref semaphore::broken().
|
||||
class broken_semaphore : public std::exception {
|
||||
public:
|
||||
/// Reports the exception reason.
|
||||
virtual const char* what() const noexcept {
|
||||
return "Semaphore broken";
|
||||
}
|
||||
};
|
||||
|
||||
/// Exception thrown when a semaphore wait operation
|
||||
/// times out.
|
||||
///
|
||||
/// \see semaphore::wait(typename timer<>::duration timeout, size_t nr)
|
||||
class semaphore_timed_out : public std::exception {
|
||||
public:
|
||||
/// Reports the exception reason.
|
||||
virtual const char* what() const noexcept {
|
||||
return "Semaphore timedout";
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Counted resource guard.
|
||||
///
|
||||
/// This is a standard computer science semaphore, adapted
|
||||
/// for futures. You can deposit units into a counter,
|
||||
/// or take them away. Taking units from the counter may wait
|
||||
/// if not enough units are available.
|
||||
///
|
||||
/// To support exceptional conditions, a \ref broken() method
|
||||
/// is provided, which causes all current waiters to stop waiting,
|
||||
/// with an exceptional future returned. This allows causing all
|
||||
/// fibers that are blocked on a semaphore to continue. This is
|
||||
/// similar to POSIX's `pthread_cancel()`, with \ref wait() acting
|
||||
/// as a cancellation point.
|
||||
class semaphore {
|
||||
private:
|
||||
size_t _count;
|
||||
struct entry {
|
||||
promise<> pr;
|
||||
size_t nr;
|
||||
timer<> tr;
|
||||
entry(promise<>&& pr_, size_t nr_) : pr(std::move(pr_)), nr(nr_) {}
|
||||
};
|
||||
std::list<entry> _wait_list;
|
||||
public:
|
||||
/// Constructs a semaphore object with a specific number of units
|
||||
/// in its internal counter. The default is 1, suitable for use as
|
||||
/// an unlocked mutex.
|
||||
///
|
||||
/// \param count number of initial units present in the counter (default 1).
|
||||
semaphore(size_t count = 1) : _count(count) {}
|
||||
/// Waits until at least a specific number of units are available in the
|
||||
/// counter, and reduces the counter by that amount of units.
|
||||
///
|
||||
/// \note Waits are serviced in FIFO order, though if several are awakened
|
||||
/// at once, they may be reordered by the scheduler.
|
||||
///
|
||||
/// \param nr Amount of units to wait for (default 1).
|
||||
/// \return a future that becomes ready when sufficient units are availble
|
||||
/// to satisfy the request. If the semaphore was \ref broken(), may
|
||||
/// contain an exception.
|
||||
future<> wait(size_t nr = 1) {
|
||||
if (_count >= nr && _wait_list.empty()) {
|
||||
_count -= nr;
|
||||
return make_ready_future<>();
|
||||
}
|
||||
promise<> pr;
|
||||
auto fut = pr.get_future();
|
||||
_wait_list.push_back(entry(std::move(pr), nr));
|
||||
return fut;
|
||||
}
|
||||
/// Waits until at least a specific number of units are available in the
|
||||
/// counter, and reduces the counter by that amount of units. If the request
|
||||
/// cannot be satisfied in time, the request is aborted.
|
||||
///
|
||||
/// \note Waits are serviced in FIFO order, though if several are awakened
|
||||
/// at once, they may be reordered by the scheduler.
|
||||
///
|
||||
/// \param timeout how long to wait.
|
||||
/// \param nr Amount of units to wait for (default 1).
|
||||
/// \return a future that becomes ready when sufficient units are availble
|
||||
/// to satisfy the request. On timeout, the future contains a
|
||||
/// \ref semaphore_timed_out exception. If the semaphore was
|
||||
/// \ref broken(), may contain an exception.
|
||||
future<> wait(typename timer<>::duration timeout, size_t nr = 1) {
|
||||
auto fut = wait(nr);
|
||||
if (!fut.available()) {
|
||||
auto& e = _wait_list.back();
|
||||
e.tr.set_callback([&e, this] {
|
||||
e.pr.set_exception(semaphore_timed_out());
|
||||
e.nr = 0;
|
||||
signal(0);
|
||||
});
|
||||
e.tr.arm(timeout);
|
||||
}
|
||||
return std::move(fut);
|
||||
}
|
||||
/// Deposits a specified number of units into the counter.
|
||||
///
|
||||
/// The counter is incremented by the specified number of units.
|
||||
/// If the new counter value is sufficient to satisfy the request
|
||||
/// of one or more waiters, their futures (in FIFO order) become
|
||||
/// ready, and the value of the counter is reduced according to
|
||||
/// the amount requested.
|
||||
///
|
||||
/// \param nr Number of units to deposit (default 1).
|
||||
void signal(size_t nr = 1) {
|
||||
_count += nr;
|
||||
while (!_wait_list.empty() && _wait_list.front().nr <= _count) {
|
||||
auto& x = _wait_list.front();
|
||||
if (x.nr) {
|
||||
_count -= x.nr;
|
||||
x.pr.set_value();
|
||||
x.tr.cancel();
|
||||
}
|
||||
_wait_list.pop_front();
|
||||
}
|
||||
}
|
||||
/// Attempts to reduce the counter value by a specified number of units.
|
||||
///
|
||||
/// If sufficient units are available in the counter, and if no
|
||||
/// other fiber is waiting, then the counter is reduced. Otherwise,
|
||||
/// nothing happens. This is useful for "opportunistic" waits where
|
||||
/// useful work can happen if the counter happens to be ready, but
|
||||
/// when it is not worthwhile to wait.
|
||||
///
|
||||
/// \param nr number of units to reduce the counter by (default 1).
|
||||
/// \return `true` if the counter had sufficient units, and was decremented.
|
||||
bool try_wait(size_t nr = 1) {
|
||||
if (_count >= nr && _wait_list.empty()) {
|
||||
_count -= nr;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/// Returns the number of units available in the counter.
|
||||
///
|
||||
/// Does not take into account any waiters.
|
||||
size_t current() const { return _count; }
|
||||
|
||||
/// Signal to waiters that an error occurred. \ref wait() will see
|
||||
/// an exceptional future<> containing a \ref broken_semaphore exception.
|
||||
/// The future is made available immediately.
|
||||
///
|
||||
/// This may only be used once per semaphore; after using it the
|
||||
/// semaphore is in an indeterminate state and should not be waited on.
|
||||
void broken() { broken(std::make_exception_ptr(broken_semaphore())); }
|
||||
|
||||
/// Signal to waiters that an error occurred. \ref wait() will see
|
||||
/// an exceptional future<> containing the provided exception parameter.
|
||||
/// The future is made available immediately.
|
||||
///
|
||||
/// This may only be used once per semaphore; after using it the
|
||||
/// semaphore is in an indeterminate state and should not be waited on.
|
||||
template <typename Exception>
|
||||
void broken(const Exception& ex) {
|
||||
broken(std::make_exception_ptr(ex));
|
||||
}
|
||||
|
||||
/// Signal to waiters that an error occurred. \ref wait() will see
|
||||
/// an exceptional future<> containing the provided exception parameter.
|
||||
/// The future is made available immediately.
|
||||
///
|
||||
/// This may only be used once per semaphore; after using it the
|
||||
/// semaphore is in an indeterminate state and should not be waited on.
|
||||
void broken(std::exception_ptr ex);
|
||||
};
|
||||
|
||||
inline
|
||||
void
|
||||
semaphore::broken(std::exception_ptr xp) {
|
||||
while (!_wait_list.empty()) {
|
||||
auto& x = _wait_list.front();
|
||||
x.pr.set_exception(xp);
|
||||
x.tr.cancel();
|
||||
_wait_list.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
#endif /* CORE_SEMAPHORE_HH_ */
|
||||
423
core/sharded.hh
423
core/sharded.hh
@@ -1,423 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "reactor.hh"
|
||||
#include "future-util.hh"
|
||||
#include "util/is_smart_ptr.hh"
|
||||
|
||||
namespace seastar {
|
||||
|
||||
/// \defgroup smp-module Multicore
|
||||
///
|
||||
/// \brief Support for exploiting multiple cores on a server.
|
||||
///
|
||||
/// Seastar supports multicore servers by using \i sharding. Each logical
|
||||
/// core (lcore) runs a separate event loop, with its own memory allocator,
|
||||
/// TCP/IP stack, and other services. Shards communicate by explicit message
|
||||
/// passing, rather than using locks and condition variables as with traditional
|
||||
/// threaded programming.
|
||||
|
||||
/// \addtogroup smp-module
|
||||
/// @{
|
||||
|
||||
/// Template helper to distribute a service across all logical cores.
|
||||
///
|
||||
/// The \c sharded template manages a sharded service, by creating
|
||||
/// a copy of the service on each logical core, providing mechanisms to communicate
|
||||
/// with each shard's copy, and a way to stop the service.
|
||||
///
|
||||
/// \tparam Service a class to be instantiated on each core. Should expose
|
||||
/// a \c stop() method that returns a \c future<>, to be called when
|
||||
/// the service is stopped.
|
||||
template <typename Service>
|
||||
class sharded {
|
||||
std::vector<Service*> _instances;
|
||||
public:
|
||||
/// Constructs an empty \c sharded object. No instances of the service are
|
||||
/// created.
|
||||
sharded() {}
|
||||
sharded(const sharded& other) = delete;
|
||||
/// Moves a \c sharded object.
|
||||
sharded(sharded&& other) = default;
|
||||
sharded& operator=(const sharded& other) = delete;
|
||||
/// Moves a \c sharded object.
|
||||
sharded& operator=(sharded& other) = default;
|
||||
/// Destroyes a \c sharded object. Must not be in a started state.
|
||||
~sharded();
|
||||
|
||||
/// Starts \c Service by constructing an instance on every logical core
|
||||
/// with a copy of \c args passed to the constructor.
|
||||
///
|
||||
/// \param args Arguments to be forwarded to \c Service constructor
|
||||
/// \return a \ref future<> that becomes ready when all instances have been
|
||||
/// constructed.
|
||||
template <typename... Args>
|
||||
future<> start(Args&&... args);
|
||||
|
||||
/// Starts \c Service by constructing an instance on a single logical core
|
||||
/// with a copy of \c args passed to the constructor.
|
||||
///
|
||||
/// \param args Arguments to be forwarded to \c Service constructor
|
||||
/// \return a \ref future<> that becomes ready when the instance has been
|
||||
/// constructed.
|
||||
template <typename... Args>
|
||||
future<> start_single(Args&&... args);
|
||||
|
||||
/// Stops all started instances and destroys them.
|
||||
///
|
||||
/// For every started instance, its \c stop() method is called, and then
|
||||
/// it is destroyed.
|
||||
future<> stop();
|
||||
|
||||
// Invoke a method on all instances of @Service.
|
||||
// The return value becomes ready when all instances have processed
|
||||
// the message.
|
||||
template <typename... Args>
|
||||
future<> invoke_on_all(future<> (Service::*func)(Args...), Args... args);
|
||||
|
||||
/// Invoke a method on all \c Service instances in parallel.
|
||||
///
|
||||
/// \param func member function to be called. Must return \c void or
|
||||
/// \c future<>.
|
||||
/// \param args arguments to be passed to \c func.
|
||||
/// \return future that becomes ready when the method has been invoked
|
||||
/// on all instances.
|
||||
template <typename... Args>
|
||||
future<> invoke_on_all(void (Service::*func)(Args...), Args... args);
|
||||
|
||||
/// Invoke a callable on all instances of \c Service.
|
||||
///
|
||||
/// \param func a callable with the signature `void (Service&)`
|
||||
/// or `future<> (Service&)`, to be called on each core
|
||||
/// with the local instance as an argument.
|
||||
/// \return a `future<>` that becomes ready when all cores have
|
||||
/// processed the message.
|
||||
template <typename Func>
|
||||
future<> invoke_on_all(Func&& func);
|
||||
|
||||
/// Invoke a method on all instances of `Service` and reduce the results using
|
||||
/// `Reducer`.
|
||||
///
|
||||
/// \see map_reduce(Iterator begin, Iterator end, Mapper&& mapper, Reducer&& r)
|
||||
template <typename Reducer, typename Ret, typename... FuncArgs, typename... Args>
|
||||
inline
|
||||
auto
|
||||
map_reduce(Reducer&& r, Ret (Service::*func)(FuncArgs...), Args&&... args)
|
||||
-> typename reducer_traits<Reducer>::future_type
|
||||
{
|
||||
unsigned c = 0;
|
||||
return ::map_reduce(_instances.begin(), _instances.end(),
|
||||
[&c, func, args = std::make_tuple(std::forward<Args>(args)...)] (Service* inst) mutable {
|
||||
return smp::submit_to(c++, [inst, func, args] () mutable {
|
||||
return apply([inst, func] (Args&&... args) mutable {
|
||||
return (inst->*func)(std::forward<Args>(args)...);
|
||||
}, std::move(args));
|
||||
});
|
||||
}, std::forward<Reducer>(r));
|
||||
}
|
||||
|
||||
/// Invoke a callable on all instances of `Service` and reduce the results using
|
||||
/// `Reducer`.
|
||||
///
|
||||
/// \see map_reduce(Iterator begin, Iterator end, Mapper&& mapper, Reducer&& r)
|
||||
template <typename Reducer, typename Func>
|
||||
inline
|
||||
auto map_reduce(Reducer&& r, Func&& func) -> typename reducer_traits<Reducer>::future_type
|
||||
{
|
||||
unsigned c = 0;
|
||||
return ::map_reduce(_instances.begin(), _instances.end(),
|
||||
[&c, &func] (Service* inst) mutable {
|
||||
return smp::submit_to(c++, [inst, func] () mutable {
|
||||
return func(*inst);
|
||||
});
|
||||
}, std::forward<Reducer>(r));
|
||||
}
|
||||
|
||||
/// Applies a map function to all shards, then reduces the output by calling a reducer function.
|
||||
///
|
||||
/// \param map callable with the signature `Value (Service&)` or
|
||||
/// `future<Value> (Service&)` (for some `Value` type).
|
||||
/// used as the second input to \c reduce
|
||||
/// \param initial initial value used as the first input to \c reduce.
|
||||
/// \param reduce binary function used to left-fold the return values of \c map
|
||||
/// into \c initial .
|
||||
///
|
||||
/// Each \c map invocation runs on the shard associated with the service.
|
||||
///
|
||||
/// \tparam Mapper unary function taking `Service&` and producing some result.
|
||||
/// \tparam Initial any value type
|
||||
/// \tparam Reduce a binary function taking two Initial values and returning an Initial
|
||||
/// \return Result of applying `map` to each instance in parallel, reduced by calling
|
||||
/// `reduce()` on each adjacent pair of results.
|
||||
template <typename Mapper, typename Initial, typename Reduce>
|
||||
inline
|
||||
future<Initial>
|
||||
map_reduce0(Mapper map, Initial initial, Reduce reduce) {
|
||||
auto wrapped_map = [this, map] (unsigned c) {
|
||||
return smp::submit_to(c, [map, inst = _instances[c]] {
|
||||
return map(*inst);
|
||||
});
|
||||
};
|
||||
return ::map_reduce(smp::all_cpus().begin(), smp::all_cpus().end(),
|
||||
std::move(wrapped_map),
|
||||
std::move(initial),
|
||||
std::move(reduce));
|
||||
}
|
||||
|
||||
/// Invoke a method on a specific instance of `Service`.
|
||||
///
|
||||
/// \param id shard id to call
|
||||
/// \param func a method of `Service`
|
||||
/// \param args arguments to be passed to `func`
|
||||
/// \return result of calling `func(args)` on the designated instance
|
||||
template <typename Ret, typename... FuncArgs, typename... Args, typename FutureRet = futurize_t<Ret>>
|
||||
FutureRet
|
||||
invoke_on(unsigned id, Ret (Service::*func)(FuncArgs...), Args&&... args) {
|
||||
using futurator = futurize<Ret>;
|
||||
auto inst = _instances[id];
|
||||
return smp::submit_to(id, [func, args = std::make_tuple(inst, std::forward<Args>(args)...)] () mutable {
|
||||
return futurator::apply(std::mem_fn(func), std::move(args));
|
||||
});
|
||||
}
|
||||
|
||||
/// Invoke a callable on a specific instance of `Service`.
|
||||
///
|
||||
/// \param id shard id to call
|
||||
/// \param func a callable with signature `Value (Service&)` or
|
||||
/// `future<Value> (Service&)` (for some `Value` type)
|
||||
/// \return result of calling `func(instance)` on the designated instance
|
||||
template <typename Func, typename Ret = futurize_t<std::result_of_t<Func(Service&)>>>
|
||||
Ret
|
||||
invoke_on(unsigned id, Func&& func) {
|
||||
auto inst = _instances[id];
|
||||
return smp::submit_to(id, [inst, func = std::forward<Func>(func)] () mutable {
|
||||
return func(*inst);
|
||||
});
|
||||
}
|
||||
|
||||
/// Gets a reference to the local instance.
|
||||
Service& local();
|
||||
|
||||
/// Checks whether the local instance has been initialized.
|
||||
bool local_is_initialized();
|
||||
};
|
||||
|
||||
template <typename Service>
|
||||
sharded<Service>::~sharded() {
|
||||
assert(_instances.empty());
|
||||
}
|
||||
|
||||
template <typename Service>
|
||||
template <typename... Args>
|
||||
future<>
|
||||
sharded<Service>::start(Args&&... args) {
|
||||
_instances.resize(smp::count);
|
||||
unsigned c = 0;
|
||||
return parallel_for_each(_instances.begin(), _instances.end(),
|
||||
[this, &c, args = std::make_tuple(std::forward<Args>(args)...)] (Service*& inst) mutable {
|
||||
return smp::submit_to(c++, [&inst, args] () mutable {
|
||||
inst = apply([] (Args&&... args) {
|
||||
return new Service(std::forward<Args>(args)...);
|
||||
}, std::move(args));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Service>
|
||||
template <typename... Args>
|
||||
future<>
|
||||
sharded<Service>::start_single(Args&&... args) {
|
||||
assert(_instances.empty());
|
||||
_instances.resize(1);
|
||||
return smp::submit_to(0, [this, args = std::make_tuple(std::forward<Args>(args)...)] () mutable {
|
||||
_instances[0] = apply([] (Args&&... args) {
|
||||
return new Service(std::forward<Args>(args)...);
|
||||
}, std::move(args));
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Service>
|
||||
future<>
|
||||
sharded<Service>::stop() {
|
||||
unsigned c = 0;
|
||||
return parallel_for_each(_instances.begin(), _instances.end(), [&c] (Service*& inst) mutable {
|
||||
return smp::submit_to(c++, [inst] () mutable {
|
||||
return inst->stop().then([inst] () mutable {
|
||||
delete inst;
|
||||
});
|
||||
});
|
||||
}).then([this] {
|
||||
_instances.clear();
|
||||
_instances = std::vector<Service*>();
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Service>
|
||||
template <typename... Args>
|
||||
inline
|
||||
future<>
|
||||
sharded<Service>::invoke_on_all(future<> (Service::*func)(Args...), Args... args) {
|
||||
unsigned c = 0;
|
||||
return parallel_for_each(_instances.begin(), _instances.end(), [&c, func, args...] (Service* inst) {
|
||||
return smp::submit_to(c++, [inst, func, args...] {
|
||||
return (inst->*func)(args...);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Service>
|
||||
template <typename... Args>
|
||||
inline
|
||||
future<>
|
||||
sharded<Service>::invoke_on_all(void (Service::*func)(Args...), Args... args) {
|
||||
unsigned c = 0;
|
||||
return parallel_for_each(_instances.begin(), _instances.end(), [&c, func, args...] (Service* inst) {
|
||||
return smp::submit_to(c++, [inst, func, args...] {
|
||||
(inst->*func)(args...);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Service>
|
||||
template <typename Func>
|
||||
inline
|
||||
future<>
|
||||
sharded<Service>::invoke_on_all(Func&& func) {
|
||||
static_assert(std::is_same<futurize_t<std::result_of_t<Func(Service&)>>, future<>>::value,
|
||||
"invoke_on_all()'s func must return void or future<>");
|
||||
unsigned c = 0;
|
||||
return parallel_for_each(_instances.begin(), _instances.end(), [&c, &func] (Service* inst) {
|
||||
return smp::submit_to(c++, [inst, func] {
|
||||
return func(*inst);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Service>
|
||||
Service& sharded<Service>::local() {
|
||||
assert(local_is_initialized());
|
||||
return *_instances[engine().cpu_id()];
|
||||
}
|
||||
|
||||
template <typename Service>
|
||||
inline bool sharded<Service>::local_is_initialized() {
|
||||
return _instances.size() > engine().cpu_id() &&
|
||||
_instances[engine().cpu_id()];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Smart pointer wrapper which makes it safe to move across CPUs.
|
||||
///
|
||||
/// \c foreign_ptr<> is a smart pointer wrapper which, unlike
|
||||
/// \ref shared_ptr and \ref lw_shared_ptr, is safe to move to a
|
||||
/// different core.
|
||||
///
|
||||
/// As seastar avoids locking, any but the most trivial objects must
|
||||
/// be destroyed on the same core they were created on, so that,
|
||||
/// for example, their destructors can unlink references to the
|
||||
/// object from various containers. In addition, for performance
|
||||
/// reasons, the shared pointer types do not use atomic operations
|
||||
/// to manage their reference counts. As a result they cannot be
|
||||
/// used on multiple cores in parallel.
|
||||
///
|
||||
/// \c foreign_ptr<> provides a solution to that problem.
|
||||
/// \c foreign_ptr<> wraps any pointer type -- raw pointer,
|
||||
/// \ref shared_ptr<>, or similar, and remembers on what core this
|
||||
/// happened. When the \c foreign_ptr<> object is destroyed, it
|
||||
/// sends a message to the original core so that the wrapped object
|
||||
/// can be safely destroyed.
|
||||
///
|
||||
/// \c foreign_ptr<> is a move-only object; it cannot be copied.
|
||||
///
|
||||
template <typename PtrType>
|
||||
class foreign_ptr {
|
||||
private:
|
||||
PtrType _value;
|
||||
unsigned _cpu;
|
||||
private:
|
||||
bool on_origin() {
|
||||
return engine().cpu_id() == _cpu;
|
||||
}
|
||||
public:
|
||||
using element_type = typename std::pointer_traits<PtrType>::element_type;
|
||||
|
||||
/// Constructs a null \c foreign_ptr<>.
|
||||
foreign_ptr()
|
||||
: _value(PtrType())
|
||||
, _cpu(engine().cpu_id()) {
|
||||
}
|
||||
/// Constructs a null \c foreign_ptr<>.
|
||||
foreign_ptr(std::nullptr_t) : foreign_ptr() {}
|
||||
/// Wraps a pointer object and remembers the current core.
|
||||
foreign_ptr(PtrType value)
|
||||
: _value(std::move(value))
|
||||
, _cpu(engine().cpu_id()) {
|
||||
}
|
||||
// The type is intentionally non-copyable because copies
|
||||
// are expensive because each copy requires across-CPU call.
|
||||
foreign_ptr(const foreign_ptr&) = delete;
|
||||
/// Moves a \c foreign_ptr<> to another object.
|
||||
foreign_ptr(foreign_ptr&& other) = default;
|
||||
/// Destroys the wrapped object on its original cpu.
|
||||
~foreign_ptr() {
|
||||
if (_value && !on_origin()) {
|
||||
smp::submit_to(_cpu, [v = std::move(_value)] () mutable {
|
||||
auto local(std::move(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
/// release the wrapped object on a local cpu. If executed on cpu
|
||||
/// other than the one object was created on object will be copied
|
||||
/// to local memory.
|
||||
element_type make_local_and_release() {
|
||||
if (on_origin()) {
|
||||
return std::move(*_value);
|
||||
} else {
|
||||
// copied to caller's cpu here
|
||||
return *_value;
|
||||
}
|
||||
}
|
||||
/// Accesses the wrapped object.
|
||||
element_type& operator*() const { return *_value; }
|
||||
/// Accesses the wrapped object.
|
||||
element_type* operator->() const { return &*_value; }
|
||||
/// Checks whether the wrapped pointer is non-null.
|
||||
operator bool() const { return static_cast<bool>(_value); }
|
||||
/// Move-assigns a \c foreign_ptr<>.
|
||||
foreign_ptr& operator=(foreign_ptr&& other) = default;
|
||||
};
|
||||
|
||||
/// Wraps a raw or smart pointer object in a \ref foreign_ptr<>.
|
||||
///
|
||||
/// \relates foreign_ptr
|
||||
template <typename T>
|
||||
foreign_ptr<T> make_foreign(T ptr) {
|
||||
return foreign_ptr<T>(std::move(ptr));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct is_smart_ptr<::foreign_ptr<T>> : std::true_type {};
|
||||
|
||||
/// @}
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "future.hh"
|
||||
#include "circular_buffer.hh"
|
||||
|
||||
namespace seastar {
|
||||
|
||||
/// \addtogroup fiber-module
|
||||
/// @{
|
||||
|
||||
/// \brief Shared/exclusive mutual exclusion.
|
||||
///
|
||||
/// Similar to \c std::shared_mutex, this class provides protection
|
||||
/// for a shared resource, with two levels of access protection: shared
|
||||
/// and exclusive. Shared access allows multiple tasks to access the
|
||||
/// shared resource concurrently, while exclusive access allows just
|
||||
/// one task to access the resource at a time.
|
||||
///
|
||||
/// Note that many seastar tasks do not require protection at all,
|
||||
/// since the seastar scheduler is not preemptive; however tasks that do
|
||||
/// (by waiting on a future) may require explicit locking.
|
||||
///
|
||||
/// The \ref with_shared(shared_mutex&, Func&&) and
|
||||
/// \ref with_lock(shared_mutex&, Func&&) provide exception-safe
|
||||
/// wrappers for use with \c shared_mutex.
|
||||
///
|
||||
/// \see semaphore simpler mutual exclusion
|
||||
class shared_mutex {
|
||||
unsigned _readers = 0;
|
||||
bool _writer = false;
|
||||
struct waiter {
|
||||
waiter(promise<>&& pr, bool for_write) : pr(std::move(pr)), for_write(for_write) {}
|
||||
promise<> pr;
|
||||
bool for_write;
|
||||
};
|
||||
circular_buffer<waiter> _waiters;
|
||||
public:
|
||||
shared_mutex() = default;
|
||||
shared_mutex(shared_mutex&&) = default;
|
||||
shared_mutex& operator=(shared_mutex&&) = default;
|
||||
shared_mutex(const shared_mutex&) = delete;
|
||||
void operator=(const shared_mutex&) = delete;
|
||||
/// Lock the \c shared_mutex for shared access
|
||||
///
|
||||
/// \return a future that becomes ready when no exclusive access
|
||||
/// is granted to anyone.
|
||||
future<> lock_shared() {
|
||||
if (!_writer && _waiters.empty()) {
|
||||
++_readers;
|
||||
return make_ready_future<>();
|
||||
}
|
||||
_waiters.emplace_back(promise<>(), false);
|
||||
return _waiters.back().pr.get_future();
|
||||
}
|
||||
/// Unlocks a \c shared_mutex after a previous call to \ref lock_shared().
|
||||
void unlock_shared() {
|
||||
--_readers;
|
||||
wake();
|
||||
}
|
||||
/// Lock the \c shared_mutex for exclusive access
|
||||
///
|
||||
/// \return a future that becomes ready when no access, shared or exclusive
|
||||
/// is granted to anyone.
|
||||
future<> lock() {
|
||||
if (!_readers && !_writer) {
|
||||
_writer = true;
|
||||
return make_ready_future<>();
|
||||
}
|
||||
_waiters.emplace_back(promise<>(), true);
|
||||
return _waiters.back().pr.get_future();
|
||||
}
|
||||
/// Unlocks a \c shared_mutex after a previous call to \ref lock().
|
||||
void unlock() {
|
||||
_writer = false;
|
||||
wake();
|
||||
}
|
||||
private:
|
||||
void wake() {
|
||||
while (!_waiters.empty()) {
|
||||
auto& w = _waiters.front();
|
||||
// note: _writer == false in wake()
|
||||
if (w.for_write) {
|
||||
if (!_readers) {
|
||||
_writer = true;
|
||||
w.pr.set_value();
|
||||
_waiters.pop_front();
|
||||
}
|
||||
break;
|
||||
} else { // for read
|
||||
++_readers;
|
||||
w.pr.set_value();
|
||||
_waiters.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Executes a function while holding shared access to a resource.
|
||||
///
|
||||
/// Executes a function while holding shared access to a resource. When
|
||||
/// the function returns, the mutex is automatically unlocked.
|
||||
///
|
||||
/// \param sm a \ref shared_mutex guarding access to the shared resource
|
||||
/// \param func callable object to invoke while the mutex is held for shared access
|
||||
/// \return whatever \c func returns, as a future
|
||||
///
|
||||
/// \relates shared_mutex
|
||||
template <typename Func>
|
||||
inline
|
||||
futurize_t<std::result_of_t<Func ()>>
|
||||
with_shared(shared_mutex& sm, Func&& func) {
|
||||
return sm.lock_shared().then([func = std::forward<Func>(func)] () mutable {
|
||||
return func();
|
||||
}).finally([&sm] {
|
||||
sm.unlock_shared();
|
||||
});
|
||||
}
|
||||
|
||||
/// Executes a function while holding exclusive access to a resource.
|
||||
///
|
||||
/// Executes a function while holding exclusive access to a resource. When
|
||||
/// the function returns, the mutex is automatically unlocked.
|
||||
///
|
||||
/// \param sm a \ref shared_mutex guarding access to the shared resource
|
||||
/// \param func callable object to invoke while the mutex is held for shared access
|
||||
/// \return whatever \c func returns, as a future
|
||||
///
|
||||
/// \relates shared_mutex
|
||||
template <typename Func>
|
||||
inline
|
||||
futurize_t<std::result_of_t<Func ()>>
|
||||
with_lock(shared_mutex& sm, Func&& func) {
|
||||
return sm.lock().then([func = std::forward<Func>(func)] () mutable {
|
||||
return func();
|
||||
}).finally([&sm] {
|
||||
sm.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
}
|
||||
@@ -1,729 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef SHARED_PTR_HH_
|
||||
#define SHARED_PTR_HH_
|
||||
|
||||
#include "shared_ptr_debug_helper.hh"
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include "util/is_smart_ptr.hh"
|
||||
|
||||
// This header defines two shared pointer facilities, lw_shared_ptr<> and
|
||||
// shared_ptr<>, both modeled after std::shared_ptr<>.
|
||||
//
|
||||
// Unlike std::shared_ptr<>, neither of these implementations are thread
|
||||
// safe, and two pointers sharing the same object must not be used in
|
||||
// different threads.
|
||||
//
|
||||
// lw_shared_ptr<> is the more lightweight variant, with a lw_shared_ptr<>
|
||||
// occupying just one machine word, and adding just one word to the shared
|
||||
// object. However, it does not support polymorphism.
|
||||
//
|
||||
// shared_ptr<> is more expensive, with a pointer occupying two machine
|
||||
// words, and with two words of overhead in the shared object. In return,
|
||||
// it does support polymorphism.
|
||||
//
|
||||
// Both variants support shared_from_this() via enable_shared_from_this<>
|
||||
// and lw_enable_shared_from_this<>().
|
||||
//
|
||||
|
||||
#ifndef DEBUG_SHARED_PTR
|
||||
using shared_ptr_counter_type = long;
|
||||
#else
|
||||
using shared_ptr_counter_type = debug_shared_ptr_counter_type;
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
class lw_shared_ptr;
|
||||
|
||||
template <typename T>
|
||||
class shared_ptr;
|
||||
|
||||
template <typename T>
|
||||
class enable_lw_shared_from_this;
|
||||
|
||||
template <typename T>
|
||||
class enable_shared_from_this;
|
||||
|
||||
template <typename T, typename... A>
|
||||
lw_shared_ptr<T> make_lw_shared(A&&... a);
|
||||
|
||||
template <typename T>
|
||||
lw_shared_ptr<T> make_lw_shared(T&& a);
|
||||
|
||||
template <typename T>
|
||||
lw_shared_ptr<T> make_lw_shared(T& a);
|
||||
|
||||
template <typename T, typename... A>
|
||||
shared_ptr<T> make_shared(A&&... a);
|
||||
|
||||
template <typename T>
|
||||
shared_ptr<T> make_shared(T&& a);
|
||||
|
||||
template <typename T, typename U>
|
||||
shared_ptr<T> static_pointer_cast(const shared_ptr<U>& p);
|
||||
|
||||
template <typename T, typename U>
|
||||
shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U>& p);
|
||||
|
||||
template <typename T, typename U>
|
||||
shared_ptr<T> const_pointer_cast(const shared_ptr<U>& p);
|
||||
|
||||
// We want to support two use cases for shared_ptr<T>:
|
||||
//
|
||||
// 1. T is any type (primitive or class type)
|
||||
//
|
||||
// 2. T is a class type that inherits from enable_shared_from_this<T>.
|
||||
//
|
||||
// In the first case, we must wrap T in an object containing the counter,
|
||||
// since T may be a primitive type and cannot be a base class.
|
||||
//
|
||||
// In the second case, we want T to reach the counter through its
|
||||
// enable_shared_from_this<> base class, so that we can implement
|
||||
// shared_from_this().
|
||||
//
|
||||
// To implement those two conflicting requirements (T alongside its counter;
|
||||
// T inherits from an object containing the counter) we use std::conditional<>
|
||||
// and some accessor functions to select between two implementations.
|
||||
|
||||
|
||||
// CRTP from this to enable shared_from_this:
|
||||
template <typename T>
|
||||
class enable_lw_shared_from_this {
|
||||
shared_ptr_counter_type _count = 0;
|
||||
using ctor = T;
|
||||
T* to_value() { return static_cast<T*>(this); }
|
||||
T* to_internal_object() { return static_cast<T*>(this); }
|
||||
protected:
|
||||
enable_lw_shared_from_this() noexcept {}
|
||||
enable_lw_shared_from_this(enable_lw_shared_from_this&&) noexcept {}
|
||||
enable_lw_shared_from_this(const enable_lw_shared_from_this&) noexcept {}
|
||||
enable_lw_shared_from_this& operator=(const enable_lw_shared_from_this&) noexcept { return *this; }
|
||||
enable_lw_shared_from_this& operator=(enable_lw_shared_from_this&&) noexcept { return *this; }
|
||||
public:
|
||||
lw_shared_ptr<T> shared_from_this();
|
||||
lw_shared_ptr<const T> shared_from_this() const;
|
||||
template <typename X>
|
||||
friend class lw_shared_ptr;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct shared_ptr_no_esft {
|
||||
shared_ptr_counter_type _count = 0;
|
||||
T _value;
|
||||
using ctor = shared_ptr_no_esft;
|
||||
|
||||
T* to_value() { return &_value; }
|
||||
shared_ptr_no_esft* to_internal_object() { return this; }
|
||||
shared_ptr_no_esft() = default;
|
||||
shared_ptr_no_esft(const T& x) : _value(x) {}
|
||||
shared_ptr_no_esft(T&& x) : _value(std::move(x)) {}
|
||||
template <typename... A>
|
||||
shared_ptr_no_esft(A&&... a) : _value(std::forward<A>(a)...) {}
|
||||
template <typename X>
|
||||
friend class lw_shared_ptr;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using shared_ptr_impl
|
||||
= std::conditional_t<
|
||||
std::is_base_of<enable_lw_shared_from_this<std::remove_const_t<T>>, T>::value,
|
||||
enable_lw_shared_from_this<std::remove_const_t<T>>,
|
||||
shared_ptr_no_esft<std::remove_const_t<T>>
|
||||
>;
|
||||
|
||||
template <typename T>
|
||||
class lw_shared_ptr {
|
||||
mutable shared_ptr_impl<T>* _p = nullptr;
|
||||
private:
|
||||
lw_shared_ptr(shared_ptr_impl<T>* p) noexcept : _p(p) {
|
||||
if (_p) {
|
||||
++_p->_count;
|
||||
}
|
||||
}
|
||||
template <typename... A>
|
||||
static lw_shared_ptr make(A&&... a) {
|
||||
return lw_shared_ptr(new typename shared_ptr_impl<T>::ctor(std::forward<A>(a)...));
|
||||
}
|
||||
public:
|
||||
using element_type = T;
|
||||
|
||||
lw_shared_ptr() noexcept = default;
|
||||
lw_shared_ptr(std::nullptr_t) noexcept : lw_shared_ptr() {}
|
||||
lw_shared_ptr(const lw_shared_ptr& x) noexcept : _p(x._p) {
|
||||
if (_p) {
|
||||
++_p->_count;
|
||||
}
|
||||
}
|
||||
lw_shared_ptr(lw_shared_ptr&& x) noexcept : _p(x._p) {
|
||||
x._p = nullptr;
|
||||
}
|
||||
~lw_shared_ptr() {
|
||||
if (_p && !--_p->_count) {
|
||||
delete _p->to_internal_object();
|
||||
}
|
||||
}
|
||||
lw_shared_ptr& operator=(const lw_shared_ptr& x) noexcept {
|
||||
if (_p != x._p) {
|
||||
this->~lw_shared_ptr();
|
||||
new (this) lw_shared_ptr(x);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
lw_shared_ptr& operator=(lw_shared_ptr&& x) noexcept {
|
||||
if (_p != x._p) {
|
||||
this->~lw_shared_ptr();
|
||||
new (this) lw_shared_ptr(std::move(x));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
lw_shared_ptr& operator=(std::nullptr_t) noexcept {
|
||||
return *this = lw_shared_ptr();
|
||||
}
|
||||
lw_shared_ptr& operator=(T&& x) noexcept {
|
||||
this->~lw_shared_ptr();
|
||||
new (this) lw_shared_ptr(make_lw_shared<T>(std::move(x)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
T& operator*() const noexcept { return *_p->to_value(); }
|
||||
T* operator->() const noexcept { return _p->to_value(); }
|
||||
T* get() const noexcept { return _p->to_value(); }
|
||||
|
||||
long int use_count() const noexcept {
|
||||
if (_p) {
|
||||
return _p->_count;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
operator lw_shared_ptr<const T>() const noexcept {
|
||||
return lw_shared_ptr<const T>(_p);
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return _p;
|
||||
}
|
||||
|
||||
bool owned() const noexcept {
|
||||
return _p->_count == 1;
|
||||
}
|
||||
|
||||
bool operator==(const lw_shared_ptr<const T>& x) const {
|
||||
return _p == x._p;
|
||||
}
|
||||
|
||||
bool operator!=(const lw_shared_ptr<const T>& x) const {
|
||||
return !operator==(x);
|
||||
}
|
||||
|
||||
bool operator==(const lw_shared_ptr<std::remove_const_t<T>>& x) const {
|
||||
return _p == x._p;
|
||||
}
|
||||
|
||||
bool operator!=(const lw_shared_ptr<std::remove_const_t<T>>& x) const {
|
||||
return !operator==(x);
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
friend class lw_shared_ptr;
|
||||
|
||||
template <typename X, typename... A>
|
||||
friend lw_shared_ptr<X> make_lw_shared(A&&...);
|
||||
|
||||
template <typename U>
|
||||
friend lw_shared_ptr<U> make_lw_shared(U&&);
|
||||
|
||||
template <typename U>
|
||||
friend lw_shared_ptr<U> make_lw_shared(U&);
|
||||
|
||||
template <typename U>
|
||||
friend class enable_lw_shared_from_this;
|
||||
};
|
||||
|
||||
template <typename T, typename... A>
|
||||
inline
|
||||
lw_shared_ptr<T> make_lw_shared(A&&... a) {
|
||||
return lw_shared_ptr<T>::make(std::forward<A>(a)...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
lw_shared_ptr<T> make_lw_shared(T&& a) {
|
||||
return lw_shared_ptr<T>::make(std::move(a));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
lw_shared_ptr<T> make_lw_shared(T& a) {
|
||||
return lw_shared_ptr<T>::make(a);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
lw_shared_ptr<T>
|
||||
enable_lw_shared_from_this<T>::shared_from_this() {
|
||||
return lw_shared_ptr<T>(this);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
lw_shared_ptr<const T>
|
||||
enable_lw_shared_from_this<T>::shared_from_this() const {
|
||||
return lw_shared_ptr<const T>(const_cast<enable_lw_shared_from_this*>(this));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline
|
||||
std::ostream& operator<<(std::ostream& out, const lw_shared_ptr<T>& p) {
|
||||
if (!p) {
|
||||
return out << "null";
|
||||
}
|
||||
return out << *p;
|
||||
}
|
||||
|
||||
// Polymorphic shared pointer class
|
||||
|
||||
struct shared_ptr_count_base {
|
||||
// destructor is responsible for fully-typed deletion
|
||||
virtual ~shared_ptr_count_base() {}
|
||||
shared_ptr_counter_type count = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct shared_ptr_count_for : shared_ptr_count_base {
|
||||
T data;
|
||||
template <typename... A>
|
||||
shared_ptr_count_for(A&&... a) : data(std::forward<A>(a)...) {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class enable_shared_from_this : private shared_ptr_count_base {
|
||||
public:
|
||||
shared_ptr<T> shared_from_this();
|
||||
shared_ptr<const T> shared_from_this() const;
|
||||
|
||||
template <typename U>
|
||||
friend class shared_ptr;
|
||||
|
||||
template <typename U, bool esft>
|
||||
friend struct shared_ptr_make_helper;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class shared_ptr {
|
||||
mutable shared_ptr_count_base* _b = nullptr;
|
||||
mutable T* _p = nullptr;
|
||||
private:
|
||||
explicit shared_ptr(shared_ptr_count_for<T>* b) noexcept : _b(b), _p(&b->data) {
|
||||
++_b->count;
|
||||
}
|
||||
shared_ptr(shared_ptr_count_base* b, T* p) noexcept : _b(b), _p(p) {
|
||||
if (_b) {
|
||||
++_b->count;
|
||||
}
|
||||
}
|
||||
explicit shared_ptr(enable_shared_from_this<std::remove_const_t<T>>* p) noexcept : _b(p), _p(static_cast<T*>(p)) {
|
||||
if (_b) {
|
||||
++_b->count;
|
||||
}
|
||||
}
|
||||
public:
|
||||
using element_type = T;
|
||||
|
||||
shared_ptr() noexcept = default;
|
||||
shared_ptr(std::nullptr_t) noexcept : shared_ptr() {}
|
||||
shared_ptr(const shared_ptr& x) noexcept
|
||||
: _b(x._b)
|
||||
, _p(x._p) {
|
||||
if (_b) {
|
||||
++_b->count;
|
||||
}
|
||||
}
|
||||
shared_ptr(shared_ptr&& x) noexcept
|
||||
: _b(x._b)
|
||||
, _p(x._p) {
|
||||
x._b = nullptr;
|
||||
x._p = nullptr;
|
||||
}
|
||||
template <typename U, typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
shared_ptr(const shared_ptr<U>& x) noexcept
|
||||
: _b(x._b)
|
||||
, _p(x._p) {
|
||||
if (_b) {
|
||||
++_b->count;
|
||||
}
|
||||
}
|
||||
template <typename U, typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
shared_ptr(shared_ptr<U>&& x) noexcept
|
||||
: _b(x._b)
|
||||
, _p(x._p) {
|
||||
x._b = nullptr;
|
||||
x._p = nullptr;
|
||||
}
|
||||
~shared_ptr() {
|
||||
if (_b && !--_b->count) {
|
||||
delete _b;
|
||||
}
|
||||
}
|
||||
shared_ptr& operator=(const shared_ptr& x) noexcept {
|
||||
if (this != &x) {
|
||||
this->~shared_ptr();
|
||||
new (this) shared_ptr(x);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
shared_ptr& operator=(shared_ptr&& x) noexcept {
|
||||
if (this != &x) {
|
||||
this->~shared_ptr();
|
||||
new (this) shared_ptr(std::move(x));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
shared_ptr& operator=(std::nullptr_t) noexcept {
|
||||
return *this = shared_ptr();
|
||||
}
|
||||
template <typename U, typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
shared_ptr& operator=(const shared_ptr<U>& x) noexcept {
|
||||
if (*this != x) {
|
||||
this->~shared_ptr();
|
||||
new (this) shared_ptr(x);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
template <typename U, typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
shared_ptr& operator=(shared_ptr<U>&& x) noexcept {
|
||||
if (*this != x) {
|
||||
this->~shared_ptr();
|
||||
new (this) shared_ptr(std::move(x));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
explicit operator bool() const noexcept {
|
||||
return _p;
|
||||
}
|
||||
T& operator*() const noexcept {
|
||||
return *_p;
|
||||
}
|
||||
T* operator->() const noexcept {
|
||||
return _p;
|
||||
}
|
||||
T* get() const noexcept {
|
||||
return _p;
|
||||
}
|
||||
long use_count() const noexcept {
|
||||
if (_b) {
|
||||
return _b->count;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <bool esft>
|
||||
struct make_helper;
|
||||
|
||||
template <typename U, typename... A>
|
||||
friend shared_ptr<U> make_shared(A&&... a);
|
||||
|
||||
template <typename U>
|
||||
friend shared_ptr<U> make_shared(U&& a);
|
||||
|
||||
template <typename V, typename U>
|
||||
friend shared_ptr<V> static_pointer_cast(const shared_ptr<U>& p);
|
||||
|
||||
template <typename V, typename U>
|
||||
friend shared_ptr<V> dynamic_pointer_cast(const shared_ptr<U>& p);
|
||||
|
||||
template <typename V, typename U>
|
||||
friend shared_ptr<V> const_pointer_cast(const shared_ptr<U>& p);
|
||||
|
||||
template <bool esft, typename... A>
|
||||
static shared_ptr make(A&&... a);
|
||||
|
||||
template <typename U>
|
||||
friend class enable_shared_from_this;
|
||||
|
||||
template <typename U, bool esft>
|
||||
friend struct shared_ptr_make_helper;
|
||||
|
||||
template <typename U>
|
||||
friend class shared_ptr;
|
||||
};
|
||||
|
||||
template <typename U, bool esft>
|
||||
struct shared_ptr_make_helper;
|
||||
|
||||
template <typename T>
|
||||
struct shared_ptr_make_helper<T, false> {
|
||||
template <typename... A>
|
||||
static shared_ptr<T> make(A&&... a) {
|
||||
return shared_ptr<T>(new shared_ptr_count_for<T>(std::forward<A>(a)...));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct shared_ptr_make_helper<T, true> {
|
||||
template <typename... A>
|
||||
static shared_ptr<T> make(A&&... a) {
|
||||
auto p = new T(std::forward<A>(a)...);
|
||||
return shared_ptr<T>(p, p);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename... A>
|
||||
inline
|
||||
shared_ptr<T>
|
||||
make_shared(A&&... a) {
|
||||
using helper = shared_ptr_make_helper<T, std::is_base_of<shared_ptr_count_base, T>::value>;
|
||||
return helper::make(std::forward<A>(a)...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
shared_ptr<T>
|
||||
make_shared(T&& a) {
|
||||
using helper = shared_ptr_make_helper<T, std::is_base_of<shared_ptr_count_base, T>::value>;
|
||||
return helper::make(std::forward<T>(a));
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline
|
||||
shared_ptr<T>
|
||||
static_pointer_cast(const shared_ptr<U>& p) {
|
||||
return shared_ptr<T>(p._b, static_cast<T*>(p._p));
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline
|
||||
shared_ptr<T>
|
||||
dynamic_pointer_cast(const shared_ptr<U>& p) {
|
||||
auto q = dynamic_cast<T*>(p._p);
|
||||
return shared_ptr<T>(q ? p._b : nullptr, q);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline
|
||||
shared_ptr<T>
|
||||
const_pointer_cast(const shared_ptr<U>& p) {
|
||||
return shared_ptr<T>(p._b, const_cast<T*>(p._p));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
shared_ptr<T>
|
||||
enable_shared_from_this<T>::shared_from_this() {
|
||||
auto unconst = reinterpret_cast<enable_shared_from_this<std::remove_const_t<T>>*>(this);
|
||||
return shared_ptr<T>(unconst);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
shared_ptr<const T>
|
||||
enable_shared_from_this<T>::shared_from_this() const {
|
||||
auto esft = const_cast<enable_shared_from_this*>(this);
|
||||
auto unconst = reinterpret_cast<enable_shared_from_this<std::remove_const_t<T>>*>(esft);
|
||||
return shared_ptr<const T>(unconst);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline
|
||||
bool
|
||||
operator==(const shared_ptr<T>& x, const shared_ptr<U>& y) {
|
||||
return x.get() == y.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator==(const shared_ptr<T>& x, std::nullptr_t) {
|
||||
return x.get() == nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator==(std::nullptr_t, const shared_ptr<T>& y) {
|
||||
return nullptr == y.get();
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline
|
||||
bool
|
||||
operator!=(const shared_ptr<T>& x, const shared_ptr<U>& y) {
|
||||
return x.get() != y.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator!=(const shared_ptr<T>& x, std::nullptr_t) {
|
||||
return x.get() != nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator!=(std::nullptr_t, const shared_ptr<T>& y) {
|
||||
return nullptr != y.get();
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline
|
||||
bool
|
||||
operator<(const shared_ptr<T>& x, const shared_ptr<U>& y) {
|
||||
return x.get() < y.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator<(const shared_ptr<T>& x, std::nullptr_t) {
|
||||
return x.get() < nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator<(std::nullptr_t, const shared_ptr<T>& y) {
|
||||
return nullptr < y.get();
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline
|
||||
bool
|
||||
operator<=(const shared_ptr<T>& x, const shared_ptr<U>& y) {
|
||||
return x.get() <= y.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator<=(const shared_ptr<T>& x, std::nullptr_t) {
|
||||
return x.get() <= nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator<=(std::nullptr_t, const shared_ptr<T>& y) {
|
||||
return nullptr <= y.get();
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline
|
||||
bool
|
||||
operator>(const shared_ptr<T>& x, const shared_ptr<U>& y) {
|
||||
return x.get() > y.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator>(const shared_ptr<T>& x, std::nullptr_t) {
|
||||
return x.get() > nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator>(std::nullptr_t, const shared_ptr<T>& y) {
|
||||
return nullptr > y.get();
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline
|
||||
bool
|
||||
operator>=(const shared_ptr<T>& x, const shared_ptr<U>& y) {
|
||||
return x.get() >= y.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator>=(const shared_ptr<T>& x, std::nullptr_t) {
|
||||
return x.get() >= nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
bool
|
||||
operator>=(std::nullptr_t, const shared_ptr<T>& y) {
|
||||
return nullptr >= y.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline
|
||||
std::ostream& operator<<(std::ostream& out, const shared_ptr<T>& p) {
|
||||
if (!p) {
|
||||
return out << "null";
|
||||
}
|
||||
return out << *p;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct shared_ptr_equal_by_value {
|
||||
bool operator()(const shared_ptr<T>& i1, const shared_ptr<T>& i2) const {
|
||||
if (bool(i1) ^ bool(i2)) {
|
||||
return false;
|
||||
}
|
||||
return !i1 || *i1 == *i2;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct shared_ptr_value_hash {
|
||||
size_t operator()(const shared_ptr<T>& p) const {
|
||||
if (p) {
|
||||
return std::hash<T>()(*p);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
namespace std {
|
||||
|
||||
template <typename T>
|
||||
struct hash<lw_shared_ptr<T>> : private hash<T*> {
|
||||
size_t operator()(const lw_shared_ptr<T>& p) const {
|
||||
return hash<T*>::operator()(p.get());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct hash<::shared_ptr<T>> : private hash<T*> {
|
||||
size_t operator()(const ::shared_ptr<T>& p) const {
|
||||
return hash<T*>::operator()(p.get());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct is_smart_ptr<::shared_ptr<T>> : std::true_type {};
|
||||
|
||||
template<typename T>
|
||||
struct is_smart_ptr<::lw_shared_ptr<T>> : std::true_type {};
|
||||
|
||||
#endif /* SHARED_PTR_HH_ */
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef DEBUG_SHARED_PTR
|
||||
|
||||
#include <thread>
|
||||
#include <cassert>
|
||||
|
||||
// A counter that is only comfortable being incremented on the cpu
|
||||
// it was created on. Useful for verifying that a shared_ptr
|
||||
// or lw_shared_ptr isn't misued across cores.
|
||||
class debug_shared_ptr_counter_type {
|
||||
long _counter = 0;
|
||||
std::thread::id _cpu = std::this_thread::get_id();
|
||||
public:
|
||||
debug_shared_ptr_counter_type(long x) : _counter(x) {}
|
||||
operator long() const {
|
||||
check();
|
||||
return _counter;
|
||||
}
|
||||
debug_shared_ptr_counter_type& operator++() {
|
||||
check();
|
||||
++_counter;
|
||||
return *this;
|
||||
}
|
||||
long operator++(int) {
|
||||
check();
|
||||
return _counter++;
|
||||
}
|
||||
debug_shared_ptr_counter_type& operator--() {
|
||||
check();
|
||||
--_counter;
|
||||
return *this;
|
||||
}
|
||||
long operator--(int) {
|
||||
check();
|
||||
return _counter--;
|
||||
}
|
||||
private:
|
||||
void check() const {
|
||||
assert(_cpu == std::this_thread::get_id());
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
572
core/slab.hh
572
core/slab.hh
@@ -1,572 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
#ifndef __SLAB_ALLOCATOR__
|
||||
#define __SLAB_ALLOCATOR__
|
||||
|
||||
#include <boost/intrusive/unordered_set.hpp>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include "core/scollectd.hh"
|
||||
#include "core/align.hh"
|
||||
#include "core/memory.hh"
|
||||
|
||||
static constexpr uint16_t SLAB_MAGIC_NUMBER = 0x51AB; // meant to be 'SLAB' :-)
|
||||
typedef uint64_t uintptr_t;
|
||||
|
||||
namespace bi = boost::intrusive;
|
||||
|
||||
/*
|
||||
* Item requirements
|
||||
* - Extend it to slab_item_base.
|
||||
* - First parameter of constructor must be uint32_t _slab_page_index.
|
||||
* - Implement get_slab_page_index() to return _slab_page_index.
|
||||
* - Implement is_unlocked() to check if Item can be evicted.
|
||||
*/
|
||||
|
||||
/*
|
||||
* slab_page_desc is 1:1 mapped to slab page.
|
||||
* footprint: 80b for each slab page.
|
||||
*/
|
||||
struct slab_page_desc {
|
||||
private:
|
||||
bi::list_member_hook<> _lru_link;
|
||||
bi::list_member_hook<> _free_pages_link;
|
||||
void *_slab_page;
|
||||
std::vector<uintptr_t> _free_objects;
|
||||
uint32_t _refcnt;
|
||||
uint32_t _index; // index into slab page vector
|
||||
uint16_t _magic;
|
||||
uint8_t _slab_class_id;
|
||||
public:
|
||||
slab_page_desc(void *slab_page, size_t objects, size_t object_size, uint8_t slab_class_id, uint32_t index)
|
||||
: _slab_page(slab_page)
|
||||
, _refcnt(0U)
|
||||
, _index(index)
|
||||
, _magic(SLAB_MAGIC_NUMBER)
|
||||
, _slab_class_id(slab_class_id)
|
||||
{
|
||||
auto object = reinterpret_cast<uintptr_t>(slab_page);
|
||||
_free_objects.reserve(objects - 1);
|
||||
for (auto i = 1u; i < objects; i++) {
|
||||
object += object_size;
|
||||
_free_objects.push_back(object);
|
||||
}
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return _free_objects.empty();
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return _free_objects.size();
|
||||
}
|
||||
|
||||
uint32_t& refcnt() {
|
||||
return _refcnt;
|
||||
}
|
||||
|
||||
uint32_t index() const {
|
||||
return _index;
|
||||
}
|
||||
|
||||
uint16_t magic() const {
|
||||
return _magic;
|
||||
}
|
||||
|
||||
uint8_t slab_class_id() const {
|
||||
return _slab_class_id;
|
||||
}
|
||||
|
||||
void* slab_page() const {
|
||||
return _slab_page;
|
||||
}
|
||||
|
||||
std::vector<uintptr_t>& free_objects() {
|
||||
return _free_objects;
|
||||
}
|
||||
|
||||
void* allocate_object() {
|
||||
assert(!_free_objects.empty());
|
||||
auto object = reinterpret_cast<void*>(_free_objects.back());
|
||||
_free_objects.pop_back();
|
||||
return object;
|
||||
}
|
||||
|
||||
void free_object(void *object) {
|
||||
_free_objects.push_back(reinterpret_cast<uintptr_t>(object));
|
||||
}
|
||||
|
||||
template<typename Item>
|
||||
friend class slab_class;
|
||||
template<typename Item>
|
||||
friend class slab_allocator;
|
||||
};
|
||||
|
||||
class slab_item_base {
|
||||
bi::list_member_hook<> _lru_link;
|
||||
|
||||
template<typename Item>
|
||||
friend class slab_class;
|
||||
};
|
||||
|
||||
template<typename Item>
|
||||
class slab_class {
|
||||
private:
|
||||
bi::list<slab_page_desc,
|
||||
bi::member_hook<slab_page_desc, bi::list_member_hook<>,
|
||||
&slab_page_desc::_free_pages_link>> _free_slab_pages;
|
||||
bi::list<slab_item_base,
|
||||
bi::member_hook<slab_item_base, bi::list_member_hook<>,
|
||||
&slab_item_base::_lru_link>> _lru;
|
||||
size_t _size; // size of objects
|
||||
uint8_t _slab_class_id;
|
||||
private:
|
||||
template<typename... Args>
|
||||
inline
|
||||
Item* create_item(void *object, uint32_t slab_page_index, Args&&... args) {
|
||||
Item *new_item = new(object) Item(slab_page_index, std::forward<Args>(args)...);
|
||||
_lru.push_front(reinterpret_cast<slab_item_base&>(*new_item));
|
||||
return new_item;
|
||||
}
|
||||
|
||||
inline
|
||||
std::pair<void *, uint32_t> evict_lru_item(std::function<void (Item& item_ref)>& erase_func) {
|
||||
if (_lru.empty()) {
|
||||
return { nullptr, 0U };
|
||||
}
|
||||
|
||||
Item& victim = reinterpret_cast<Item&>(_lru.back());
|
||||
uint32_t index = victim.get_slab_page_index();
|
||||
assert(victim.is_unlocked());
|
||||
_lru.erase(_lru.iterator_to(reinterpret_cast<slab_item_base&>(victim)));
|
||||
// WARNING: You need to make sure that erase_func will not release victim back to slab.
|
||||
erase_func(victim);
|
||||
|
||||
return { reinterpret_cast<void*>(&victim), index };
|
||||
}
|
||||
public:
|
||||
slab_class(size_t size, uint8_t slab_class_id)
|
||||
: _size(size)
|
||||
, _slab_class_id(slab_class_id)
|
||||
{
|
||||
}
|
||||
slab_class(slab_class&&) = default;
|
||||
~slab_class() {
|
||||
_free_slab_pages.clear();
|
||||
_lru.clear();
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return _size;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return _free_slab_pages.empty();
|
||||
}
|
||||
|
||||
bool has_no_slab_pages() const {
|
||||
return _lru.empty();
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
Item *create(Args&&... args) {
|
||||
assert(!_free_slab_pages.empty());
|
||||
auto& desc = _free_slab_pages.back();
|
||||
auto object = desc.allocate_object();
|
||||
if (desc.empty()) {
|
||||
// if empty, remove desc from the list of slab pages with free objects.
|
||||
_free_slab_pages.erase(_free_slab_pages.iterator_to(desc));
|
||||
}
|
||||
|
||||
return create_item(object, desc.index(), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
Item *create_from_new_page(uint64_t max_object_size, uint32_t slab_page_index,
|
||||
std::function<void (slab_page_desc& desc)> insert_slab_page_desc,
|
||||
Args&&... args) {
|
||||
// allocate slab page.
|
||||
constexpr size_t alignment = std::alignment_of<Item>::value;
|
||||
void *slab_page = aligned_alloc(alignment, max_object_size);
|
||||
if (!slab_page) {
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
// allocate descriptor to slab page.
|
||||
slab_page_desc *desc = nullptr;
|
||||
assert(_size % alignment == 0);
|
||||
try {
|
||||
auto objects = max_object_size / _size;
|
||||
desc = new slab_page_desc(slab_page, objects, _size, _slab_class_id, slab_page_index);
|
||||
} catch (const std::bad_alloc& e) {
|
||||
::free(slab_page);
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
|
||||
_free_slab_pages.push_front(*desc);
|
||||
insert_slab_page_desc(*desc);
|
||||
|
||||
// first object from the allocated slab page is returned.
|
||||
return create_item(slab_page, slab_page_index, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
Item *create_from_lru(std::function<void (Item& item_ref)>& erase_func, Args&&... args) {
|
||||
auto ret = evict_lru_item(erase_func);
|
||||
if (!ret.first) {
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
return create_item(ret.first, ret.second, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void free_item(Item *item, slab_page_desc& desc) {
|
||||
void *object = item;
|
||||
_lru.erase(_lru.iterator_to(reinterpret_cast<slab_item_base&>(*item)));
|
||||
desc.free_object(object);
|
||||
if (desc.size() == 1) {
|
||||
// push back desc into the list of slab pages with free objects.
|
||||
_free_slab_pages.push_back(desc);
|
||||
}
|
||||
}
|
||||
|
||||
void touch_item(Item *item) {
|
||||
auto& item_ref = reinterpret_cast<slab_item_base&>(*item);
|
||||
_lru.erase(_lru.iterator_to(item_ref));
|
||||
_lru.push_front(item_ref);
|
||||
}
|
||||
|
||||
void remove_item_from_lru(Item *item) {
|
||||
auto& item_ref = reinterpret_cast<slab_item_base&>(*item);
|
||||
_lru.erase(_lru.iterator_to(item_ref));
|
||||
}
|
||||
|
||||
void insert_item_into_lru(Item *item) {
|
||||
auto& item_ref = reinterpret_cast<slab_item_base&>(*item);
|
||||
_lru.push_front(item_ref);
|
||||
}
|
||||
|
||||
void remove_desc_from_free_list(slab_page_desc& desc) {
|
||||
assert(desc.slab_class_id() == _slab_class_id);
|
||||
_free_slab_pages.erase(_free_slab_pages.iterator_to(desc));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Item>
|
||||
class slab_allocator {
|
||||
private:
|
||||
std::vector<size_t> _slab_class_sizes;
|
||||
std::vector<slab_class<Item>> _slab_classes;
|
||||
std::vector<scollectd::registration> _registrations;
|
||||
// erase_func() is used to remove the item from the cache using slab.
|
||||
std::function<void (Item& item_ref)> _erase_func;
|
||||
std::vector<slab_page_desc*> _slab_pages_vector;
|
||||
bi::list<slab_page_desc,
|
||||
bi::member_hook<slab_page_desc, bi::list_member_hook<>,
|
||||
&slab_page_desc::_lru_link>> _slab_page_desc_lru;
|
||||
uint64_t _max_object_size;
|
||||
uint64_t _available_slab_pages;
|
||||
struct collectd_stats {
|
||||
uint64_t allocs;
|
||||
uint64_t frees;
|
||||
} _stats;
|
||||
memory::reclaimer *_reclaimer = nullptr;
|
||||
bool _reclaimed = false;
|
||||
private:
|
||||
void evict_lru_slab_page() {
|
||||
if (_slab_page_desc_lru.empty()) {
|
||||
// NOTE: Nothing to evict. If this happens, it implies that all
|
||||
// slab pages in the slab are being used at the same time.
|
||||
// That being said, this event is very unlikely to happen.
|
||||
return;
|
||||
}
|
||||
// get descriptor of the least-recently-used slab page and related info.
|
||||
auto& desc = _slab_page_desc_lru.back();
|
||||
assert(desc.refcnt() == 0);
|
||||
uint8_t slab_class_id = desc.slab_class_id();
|
||||
auto slab_class = get_slab_class(slab_class_id);
|
||||
void *slab_page = desc.slab_page();
|
||||
|
||||
auto& free_objects = desc.free_objects();
|
||||
if (!desc.empty()) {
|
||||
// if not empty, remove desc from the list of slab pages with free objects.
|
||||
slab_class->remove_desc_from_free_list(desc);
|
||||
// and sort the array of free objects for binary search later on.
|
||||
std::sort(free_objects.begin(), free_objects.end());
|
||||
}
|
||||
// remove desc from the list of slab page descriptors.
|
||||
_slab_page_desc_lru.erase(_slab_page_desc_lru.iterator_to(desc));
|
||||
// remove desc from the slab page vector.
|
||||
_slab_pages_vector[desc.index()] = nullptr;
|
||||
|
||||
// Iterate through objects in the slab page and if the object is an allocated
|
||||
// item, the item should be removed from LRU and then erased.
|
||||
uintptr_t object = reinterpret_cast<uintptr_t>(slab_page);
|
||||
auto object_size = slab_class->size();
|
||||
auto objects = _max_object_size / object_size;
|
||||
for (auto i = 0u; i < objects; i++, object += object_size) {
|
||||
if (!desc.empty()) {
|
||||
// if binary_search returns true, it means that object at the current
|
||||
// offset isn't an item.
|
||||
if (std::binary_search(free_objects.begin(), free_objects.end(), object)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Item* item = reinterpret_cast<Item*>(object);
|
||||
assert(item->is_unlocked());
|
||||
slab_class->remove_item_from_lru(item);
|
||||
_erase_func(*item);
|
||||
_stats.frees++;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
printf("lru slab page eviction succeeded! desc_empty?=%d\n", desc.empty());
|
||||
#endif
|
||||
::free(slab_page); // free slab page object
|
||||
delete &desc; // free its descriptor
|
||||
}
|
||||
|
||||
/*
|
||||
* Reclaim the least recently used slab page that is unused.
|
||||
*/
|
||||
void reclaim() {
|
||||
// once reclaimer was called, slab pages should no longer be allocated, as the
|
||||
// memory used by slab is supposed to be calibrated.
|
||||
_reclaimed = true;
|
||||
// FIXME: Should reclaim() only evict a single slab page at a time?
|
||||
evict_lru_slab_page();
|
||||
}
|
||||
|
||||
void initialize_slab_allocator(double growth_factor, uint64_t limit) {
|
||||
constexpr size_t alignment = std::alignment_of<Item>::value;
|
||||
constexpr size_t initial_size = 96;
|
||||
size_t size = initial_size; // initial object size
|
||||
uint8_t slab_class_id = 0U;
|
||||
|
||||
while (_max_object_size / size > 1) {
|
||||
size = align_up(size, alignment);
|
||||
_slab_class_sizes.push_back(size);
|
||||
_slab_classes.emplace_back(size, slab_class_id);
|
||||
size *= growth_factor;
|
||||
assert(slab_class_id < std::numeric_limits<uint8_t>::max());
|
||||
slab_class_id++;
|
||||
}
|
||||
_slab_class_sizes.push_back(_max_object_size);
|
||||
_slab_classes.emplace_back(_max_object_size, slab_class_id);
|
||||
|
||||
// If slab limit is zero, enable reclaimer.
|
||||
if (!limit) {
|
||||
_reclaimer = new memory::reclaimer([this] { reclaim(); });
|
||||
} else {
|
||||
_slab_pages_vector.reserve(_available_slab_pages);
|
||||
}
|
||||
}
|
||||
|
||||
slab_class<Item>* get_slab_class(const size_t size) {
|
||||
// given a size, find slab class with binary search.
|
||||
auto i = std::lower_bound(_slab_class_sizes.begin(), _slab_class_sizes.end(), size);
|
||||
if (i == _slab_class_sizes.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto dist = std::distance(_slab_class_sizes.begin(), i);
|
||||
return &_slab_classes[dist];
|
||||
}
|
||||
|
||||
slab_class<Item>* get_slab_class(const uint8_t slab_class_id) {
|
||||
assert(slab_class_id >= 0 && slab_class_id < _slab_classes.size());
|
||||
return &_slab_classes[slab_class_id];
|
||||
}
|
||||
|
||||
void register_collectd_metrics() {
|
||||
auto add = [this] (auto type_name, auto name, auto data_type, auto func) {
|
||||
_registrations.push_back(
|
||||
scollectd::add_polled_metric(scollectd::type_instance_id("slab",
|
||||
scollectd::per_cpu_plugin_instance,
|
||||
type_name, name),
|
||||
scollectd::make_typed(data_type, func)));
|
||||
};
|
||||
|
||||
add("total_operations", "malloc", scollectd::data_type::DERIVE, [&] { return _stats.allocs; });
|
||||
add("total_operations", "free", scollectd::data_type::DERIVE, [&] { return _stats.frees; });
|
||||
add("objects", "malloc", scollectd::data_type::GAUGE, [&] { return _stats.allocs - _stats.frees; });
|
||||
}
|
||||
|
||||
inline slab_page_desc& get_slab_page_desc(Item *item)
|
||||
{
|
||||
auto desc = _slab_pages_vector[item->get_slab_page_index()];
|
||||
assert(desc != nullptr);
|
||||
assert(desc->magic() == SLAB_MAGIC_NUMBER);
|
||||
return *desc;
|
||||
}
|
||||
|
||||
inline bool can_allocate_page(slab_class<Item>& sc) {
|
||||
return (_reclaimer && !_reclaimed) ||
|
||||
(_available_slab_pages > 0 || sc.has_no_slab_pages());
|
||||
}
|
||||
public:
|
||||
slab_allocator(double growth_factor, uint64_t limit, uint64_t max_object_size)
|
||||
: _max_object_size(max_object_size)
|
||||
, _available_slab_pages(limit / max_object_size)
|
||||
{
|
||||
initialize_slab_allocator(growth_factor, limit);
|
||||
register_collectd_metrics();
|
||||
}
|
||||
|
||||
slab_allocator(double growth_factor, uint64_t limit, uint64_t max_object_size,
|
||||
std::function<void (Item& item_ref)> erase_func)
|
||||
: _erase_func(std::move(erase_func))
|
||||
, _max_object_size(max_object_size)
|
||||
, _available_slab_pages(limit / max_object_size)
|
||||
{
|
||||
initialize_slab_allocator(growth_factor, limit);
|
||||
register_collectd_metrics();
|
||||
}
|
||||
|
||||
~slab_allocator()
|
||||
{
|
||||
_slab_page_desc_lru.clear();
|
||||
for (auto desc : _slab_pages_vector) {
|
||||
if (!desc) {
|
||||
continue;
|
||||
}
|
||||
::free(desc->slab_page());
|
||||
delete desc;
|
||||
}
|
||||
_registrations.clear();
|
||||
delete _reclaimer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an item from a given slab class based on requested size.
|
||||
*/
|
||||
template<typename... Args>
|
||||
Item* create(const size_t size, Args&&... args) {
|
||||
auto slab_class = get_slab_class(size);
|
||||
if (!slab_class) {
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
|
||||
Item *item = nullptr;
|
||||
if (!slab_class->empty()) {
|
||||
item = slab_class->create(std::forward<Args>(args)...);
|
||||
_stats.allocs++;
|
||||
} else {
|
||||
if (can_allocate_page(*slab_class)) {
|
||||
auto index_to_insert = _slab_pages_vector.size();
|
||||
item = slab_class->create_from_new_page(_max_object_size, index_to_insert,
|
||||
[this](slab_page_desc& desc) {
|
||||
if (_reclaimer) {
|
||||
// insert desc into the LRU list of slab page descriptors.
|
||||
_slab_page_desc_lru.push_front(desc);
|
||||
}
|
||||
// insert desc into the slab page vector.
|
||||
_slab_pages_vector.push_back(&desc);
|
||||
},
|
||||
std::forward<Args>(args)...);
|
||||
if (_available_slab_pages > 0) {
|
||||
_available_slab_pages--;
|
||||
}
|
||||
_stats.allocs++;
|
||||
} else if (_erase_func) {
|
||||
item = slab_class->create_from_lru(_erase_func, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
void lock_item(Item *item) {
|
||||
auto& desc = get_slab_page_desc(item);
|
||||
if (_reclaimer) {
|
||||
auto& refcnt = desc.refcnt();
|
||||
|
||||
if (++refcnt == 1) {
|
||||
// remove slab page descriptor from list of slab page descriptors.
|
||||
_slab_page_desc_lru.erase(_slab_page_desc_lru.iterator_to(desc));
|
||||
}
|
||||
}
|
||||
// remove item from the lru of its slab class.
|
||||
auto slab_class = get_slab_class(desc.slab_class_id());
|
||||
slab_class->remove_item_from_lru(item);
|
||||
}
|
||||
|
||||
void unlock_item(Item *item) {
|
||||
auto& desc = get_slab_page_desc(item);
|
||||
if (_reclaimer) {
|
||||
auto& refcnt = desc.refcnt();
|
||||
|
||||
if (--refcnt == 0) {
|
||||
// insert slab page descriptor back into list of slab page descriptors.
|
||||
_slab_page_desc_lru.push_front(desc);
|
||||
}
|
||||
}
|
||||
// insert item into the lru of its slab class.
|
||||
auto slab_class = get_slab_class(desc.slab_class_id());
|
||||
slab_class->insert_item_into_lru(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free an item back to its original slab class.
|
||||
*/
|
||||
void free(Item *item) {
|
||||
if (item) {
|
||||
auto& desc = get_slab_page_desc(item);
|
||||
auto slab_class = get_slab_class(desc.slab_class_id());
|
||||
slab_class->free_item(item, desc);
|
||||
_stats.frees++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update item position in the LRU of its slab class.
|
||||
*/
|
||||
void touch(Item *item) {
|
||||
if (item) {
|
||||
auto& desc = get_slab_page_desc(item);
|
||||
auto slab_class = get_slab_class(desc.slab_class_id());
|
||||
slab_class->touch_item(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function: Print all available slab classes and their respective properties.
|
||||
*/
|
||||
void print_slab_classes() {
|
||||
auto class_id = 0;
|
||||
for (auto& slab_class : _slab_classes) {
|
||||
size_t size = slab_class.size();
|
||||
printf("slab[%3d]\tsize: %10lu\tper-slab-page: %5lu\n", class_id, size, _max_object_size / size);
|
||||
class_id++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function: Useful for getting a slab class' chunk size from a size parameter.
|
||||
*/
|
||||
size_t class_size(const size_t size) {
|
||||
auto slab_class = get_slab_class(size);
|
||||
return (slab_class) ? slab_class->size() : 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* __SLAB_ALLOCATOR__ */
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
|
||||
#include "core/shared_ptr.hh"
|
||||
#include "core/reactor.hh"
|
||||
#include "core/future.hh"
|
||||
|
||||
template <typename Clock = std::chrono::high_resolution_clock, typename Rep, typename Period>
|
||||
future<> sleep(std::chrono::duration<Rep, Period> dur) {
|
||||
struct sleeper {
|
||||
promise<> done;
|
||||
timer<Clock> tmr;
|
||||
sleeper(std::chrono::duration<Rep, Period> dur)
|
||||
: tmr([this] { done.set_value(); })
|
||||
{
|
||||
tmr.arm(dur);
|
||||
}
|
||||
};
|
||||
sleeper *s = new sleeper(dur);
|
||||
future<> fut = s->done.get_future();
|
||||
return fut.then([s] { delete s; });
|
||||
}
|
||||
688
core/sstring.hh
688
core/sstring.hh
@@ -1,688 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2014 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef SSTRING_HH_
|
||||
#define SSTRING_HH_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <cstdio>
|
||||
#include <type_traits>
|
||||
#include <experimental/string_view>
|
||||
#include "core/temporary_buffer.hh"
|
||||
|
||||
template <typename char_type, typename Size, Size max_size>
|
||||
class basic_sstring {
|
||||
static_assert(
|
||||
(std::is_same<char_type, char>::value
|
||||
|| std::is_same<char_type, signed char>::value
|
||||
|| std::is_same<char_type, unsigned char>::value),
|
||||
"basic_sstring only supports single byte char types");
|
||||
union contents {
|
||||
struct external_type {
|
||||
char_type* str;
|
||||
Size size;
|
||||
int8_t pad;
|
||||
} external;
|
||||
struct internal_type {
|
||||
char_type str[max_size];
|
||||
int8_t size;
|
||||
} internal;
|
||||
static_assert(sizeof(external_type) <= sizeof(internal_type), "max_size too small");
|
||||
static_assert(max_size <= 127, "max_size too large");
|
||||
} u;
|
||||
bool is_internal() const noexcept {
|
||||
return u.internal.size >= 0;
|
||||
}
|
||||
bool is_external() const noexcept {
|
||||
return !is_internal();
|
||||
}
|
||||
const char_type* str() const {
|
||||
return is_internal() ? u.internal.str : u.external.str;
|
||||
}
|
||||
char_type* str() {
|
||||
return is_internal() ? u.internal.str : u.external.str;
|
||||
}
|
||||
public:
|
||||
using value_type = char_type;
|
||||
using traits_type = std::char_traits<char_type>;
|
||||
using allocator_type = std::allocator<char_type>;
|
||||
using reference = char_type&;
|
||||
using const_reference = const char_type&;
|
||||
using pointer = char_type*;
|
||||
using const_pointer = const char_type*;
|
||||
using iterator = char_type*;
|
||||
using const_iterator = const char_type*;
|
||||
// FIXME: add reverse_iterator and friend
|
||||
using difference_type = ssize_t; // std::make_signed_t<Size> can be too small
|
||||
using size_type = Size;
|
||||
static constexpr size_type npos = static_cast<size_type>(-1);
|
||||
public:
|
||||
struct initialized_later {};
|
||||
|
||||
basic_sstring() noexcept {
|
||||
u.internal.size = 0;
|
||||
u.internal.str[0] = '\0';
|
||||
}
|
||||
basic_sstring(const basic_sstring& x) {
|
||||
if (x.is_internal()) {
|
||||
u.internal = x.u.internal;
|
||||
} else {
|
||||
u.internal.size = -1;
|
||||
u.external.str = reinterpret_cast<char_type*>(std::malloc(x.u.external.size + 1));
|
||||
if (!u.external.str) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
std::copy(x.u.external.str, x.u.external.str + x.u.external.size + 1, u.external.str);
|
||||
u.external.size = x.u.external.size;
|
||||
}
|
||||
}
|
||||
basic_sstring(basic_sstring&& x) noexcept {
|
||||
u = x.u;
|
||||
x.u.internal.size = 0;
|
||||
x.u.internal.str[0] = '\0';
|
||||
}
|
||||
basic_sstring(initialized_later, size_t size) {
|
||||
if (size_type(size) != size) {
|
||||
throw std::overflow_error("sstring overflow");
|
||||
}
|
||||
if (size + 1 <= sizeof(u.internal.str)) {
|
||||
u.internal.str[size] = '\0';
|
||||
u.internal.size = size;
|
||||
} else {
|
||||
u.internal.size = -1;
|
||||
u.external.str = reinterpret_cast<char_type*>(std::malloc(size + 1));
|
||||
if (!u.external.str) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
u.external.size = size;
|
||||
u.external.str[size] = '\0';
|
||||
}
|
||||
}
|
||||
basic_sstring(const char_type* x, size_t size) {
|
||||
if (size_type(size) != size) {
|
||||
throw std::overflow_error("sstring overflow");
|
||||
}
|
||||
if (size + 1 <= sizeof(u.internal.str)) {
|
||||
std::copy(x, x + size, u.internal.str);
|
||||
u.internal.str[size] = '\0';
|
||||
u.internal.size = size;
|
||||
} else {
|
||||
u.internal.size = -1;
|
||||
u.external.str = reinterpret_cast<char_type*>(std::malloc(size + 1));
|
||||
if (!u.external.str) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
u.external.size = size;
|
||||
std::copy(x, x + size, u.external.str);
|
||||
u.external.str[size] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
basic_sstring(size_t size, char_type x) : basic_sstring(initialized_later(), size) {
|
||||
memset(begin(), x, size);
|
||||
}
|
||||
|
||||
basic_sstring(const char* x) : basic_sstring(reinterpret_cast<const char_type*>(x), std::strlen(x)) {}
|
||||
basic_sstring(std::basic_string<char_type>& x) : basic_sstring(x.c_str(), x.size()) {}
|
||||
basic_sstring(std::initializer_list<char_type> x) : basic_sstring(x.begin(), x.end() - x.begin()) {}
|
||||
basic_sstring(const char_type* b, const char_type* e) : basic_sstring(b, e - b) {}
|
||||
basic_sstring(const std::basic_string<char_type>& s)
|
||||
: basic_sstring(s.data(), s.size()) {}
|
||||
template <typename InputIterator>
|
||||
basic_sstring(InputIterator first, InputIterator last)
|
||||
: basic_sstring(initialized_later(), std::distance(first, last)) {
|
||||
std::copy(first, last, begin());
|
||||
}
|
||||
~basic_sstring() noexcept {
|
||||
if (is_external()) {
|
||||
std::free(u.external.str);
|
||||
}
|
||||
}
|
||||
basic_sstring& operator=(const basic_sstring& x) {
|
||||
basic_sstring tmp(x);
|
||||
swap(tmp);
|
||||
return *this;
|
||||
}
|
||||
basic_sstring& operator=(basic_sstring&& x) noexcept {
|
||||
if (this != &x) {
|
||||
swap(x);
|
||||
x.reset();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
operator std::basic_string<char_type>() const {
|
||||
return { str(), size() };
|
||||
}
|
||||
size_t size() const noexcept {
|
||||
return is_internal() ? u.internal.size : u.external.size;
|
||||
}
|
||||
|
||||
size_t length() const noexcept {
|
||||
return size();
|
||||
}
|
||||
|
||||
size_t find(char_type t, size_t pos = 0) const noexcept {
|
||||
const char_type* it = str() + pos;
|
||||
const char_type* end = str() + size();
|
||||
while (it < end) {
|
||||
if (*it == t) {
|
||||
return it - str();
|
||||
}
|
||||
it++;
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
size_t find(const basic_sstring& s, size_t pos = 0) const noexcept {
|
||||
const char_type* it = str() + pos;
|
||||
const char_type* end = str() + size();
|
||||
const char_type* c_str = s.str();
|
||||
const char_type* c_str_end = s.str() + s.size();
|
||||
|
||||
while (it < end) {
|
||||
auto i = it;
|
||||
auto j = c_str;
|
||||
while ( i < end && j < c_str_end && *i == *j) {
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
if (j == c_str_end) {
|
||||
return it - str();
|
||||
}
|
||||
it++;
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
/**
|
||||
* find_last_of find the last occurrence of c in the string.
|
||||
* When pos is specified, the search only includes characters
|
||||
* at or before position pos.
|
||||
*
|
||||
*/
|
||||
size_t find_last_of (char_type c, size_t pos = npos) const noexcept {
|
||||
const char_type* str_start = str();
|
||||
if (size()) {
|
||||
if (pos >= size()) {
|
||||
pos = size() - 1;
|
||||
}
|
||||
const char_type* p = str_start + pos + 1;
|
||||
do {
|
||||
p--;
|
||||
if (*p == c) {
|
||||
return (p - str_start);
|
||||
}
|
||||
} while (p != str_start);
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a C substring.
|
||||
* @param s The C string to append.
|
||||
* @param n The number of characters to append.
|
||||
* @return Reference to this string.
|
||||
*/
|
||||
basic_sstring& append (const char_type* s, size_t n) {
|
||||
basic_sstring ret(initialized_later(), size() + n);
|
||||
std::copy(begin(), end(), ret.begin());
|
||||
std::copy(s, s + n, ret.begin() + size());
|
||||
*this = std::move(ret);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace characters with a value of a C style substring.
|
||||
*
|
||||
*/
|
||||
basic_sstring& replace(size_type pos, size_type n1, const char_type* s,
|
||||
size_type n2) {
|
||||
if (pos > size()) {
|
||||
throw std::out_of_range("sstring::replace out of range");
|
||||
}
|
||||
|
||||
if (n1 > size() - pos) {
|
||||
n1 = size() - pos;
|
||||
}
|
||||
|
||||
if (n1 == n2) {
|
||||
if (n2) {
|
||||
std::copy(s, s + n2, begin() + pos);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
basic_sstring ret(initialized_later(), size() + n2 - n1);
|
||||
char_type* p= ret.begin();
|
||||
std::copy(begin(), begin() + pos, p);
|
||||
p += pos;
|
||||
if (n2) {
|
||||
std::copy(s, s + n2, p);
|
||||
}
|
||||
p += n2;
|
||||
std::copy(begin() + pos + n1, end(), p);
|
||||
*this = std::move(ret);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class InputIterator>
|
||||
basic_sstring& replace (const_iterator i1, const_iterator i2,
|
||||
InputIterator first, InputIterator last) {
|
||||
if (i1 < begin() || i1 > end() || i2 < begin()) {
|
||||
throw std::out_of_range("sstring::replace out of range");
|
||||
}
|
||||
if (i2 > end()) {
|
||||
i2 = end();
|
||||
}
|
||||
|
||||
if (i2 - i1 == last - first) {
|
||||
//in place replacement
|
||||
std::copy(first, last, const_cast<char_type*>(i1));
|
||||
return *this;
|
||||
}
|
||||
basic_sstring ret(initialized_later(), size() + (last - first) - (i2 - i1));
|
||||
char_type* p = ret.begin();
|
||||
p = std::copy(cbegin(), i1, p);
|
||||
p = std::copy(first, last, p);
|
||||
std::copy(i2, cend(), p);
|
||||
*this = std::move(ret);
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator erase(iterator first, iterator last) {
|
||||
size_t pos = first - begin();
|
||||
replace(pos, last - first, nullptr, 0);
|
||||
return begin() + pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts additional characters into the string right before
|
||||
* the character indicated by p.
|
||||
*/
|
||||
template <class InputIterator>
|
||||
void insert(const_iterator p, InputIterator beg, InputIterator end) {
|
||||
replace(p, p, beg, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a read/write reference to the data at the last
|
||||
* element of the string.
|
||||
* This function shall not be called on empty strings.
|
||||
*/
|
||||
reference
|
||||
back() noexcept {
|
||||
return operator[](size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a read-only (constant) reference to the data at the last
|
||||
* element of the string.
|
||||
* This function shall not be called on empty strings.
|
||||
*/
|
||||
const_reference
|
||||
back() const noexcept {
|
||||
return operator[](size() - 1);
|
||||
}
|
||||
|
||||
basic_sstring substr(size_t from, size_t len = npos) const {
|
||||
if (from > size()) {
|
||||
throw std::out_of_range("sstring::substr out of range");
|
||||
}
|
||||
if (len > size() - from) {
|
||||
len = size() - from;
|
||||
}
|
||||
if (len == 0) {
|
||||
return "";
|
||||
}
|
||||
return { str() + from , len };
|
||||
}
|
||||
|
||||
const char_type& at(size_t pos) const {
|
||||
if (pos >= size()) {
|
||||
throw std::out_of_range("sstring::at out of range");
|
||||
}
|
||||
return *(str() + pos);
|
||||
}
|
||||
|
||||
char_type& at(size_t pos) {
|
||||
if (pos >= size()) {
|
||||
throw std::out_of_range("sstring::at out of range");
|
||||
}
|
||||
return *(str() + pos);
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return u.internal.size == 0;
|
||||
}
|
||||
void reset() noexcept {
|
||||
if (is_external()) {
|
||||
std::free(u.external.str);
|
||||
}
|
||||
u.internal.size = 0;
|
||||
u.internal.str[0] = '\0';
|
||||
}
|
||||
temporary_buffer<char_type> release() && {
|
||||
if (is_external()) {
|
||||
auto ptr = u.external.str;
|
||||
auto size = u.external.size;
|
||||
u.external.str = nullptr;
|
||||
u.external.size = 0;
|
||||
return temporary_buffer<char_type>(ptr, size, make_free_deleter(ptr));
|
||||
} else {
|
||||
auto buf = temporary_buffer<char_type>(u.internal.size);
|
||||
std::copy(u.internal.str, u.internal.str + u.internal.size, buf.get_write());
|
||||
u.internal.size = 0;
|
||||
u.internal.str[0] = '\0';
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
int compare(const basic_sstring& x) const noexcept {
|
||||
auto n = traits_type::compare(begin(), x.begin(), std::min(size(), x.size()));
|
||||
if (n != 0) {
|
||||
return n;
|
||||
}
|
||||
if (size() < x.size()) {
|
||||
return -1;
|
||||
} else if (size() > x.size()) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int compare(size_t pos, size_t sz, const basic_sstring& x) const {
|
||||
if (pos > size()) {
|
||||
throw std::out_of_range("pos larger than string size");
|
||||
}
|
||||
|
||||
sz = std::min(size() - pos, sz);
|
||||
auto n = traits_type::compare(begin() + pos, x.begin(), std::min(sz, x.size()));
|
||||
if (n != 0) {
|
||||
return n;
|
||||
}
|
||||
if (sz < x.size()) {
|
||||
return -1;
|
||||
} else if (sz > x.size()) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void swap(basic_sstring& x) noexcept {
|
||||
contents tmp;
|
||||
tmp = x.u;
|
||||
x.u = u;
|
||||
u = tmp;
|
||||
}
|
||||
const char_type* c_str() const {
|
||||
return str();
|
||||
}
|
||||
const char_type* begin() const { return str(); }
|
||||
const char_type* end() const { return str() + size(); }
|
||||
const char_type* cbegin() const { return str(); }
|
||||
const char_type* cend() const { return str() + size(); }
|
||||
char_type* begin() { return str(); }
|
||||
char_type* end() { return str() + size(); }
|
||||
bool operator==(const basic_sstring& x) const {
|
||||
return size() == x.size() && std::equal(begin(), end(), x.begin());
|
||||
}
|
||||
bool operator!=(const basic_sstring& x) const {
|
||||
return !operator==(x);
|
||||
}
|
||||
bool operator<(const basic_sstring& x) const {
|
||||
return compare(x) < 0;
|
||||
}
|
||||
basic_sstring operator+(const basic_sstring& x) const {
|
||||
basic_sstring ret(initialized_later(), size() + x.size());
|
||||
std::copy(begin(), end(), ret.begin());
|
||||
std::copy(x.begin(), x.end(), ret.begin() + size());
|
||||
return ret;
|
||||
}
|
||||
basic_sstring& operator+=(const basic_sstring& x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
char_type& operator[](size_type pos) {
|
||||
return str()[pos];
|
||||
}
|
||||
const char_type& operator[](size_type pos) const {
|
||||
return str()[pos];
|
||||
}
|
||||
|
||||
operator std::experimental::basic_string_view<char_type>() const {
|
||||
return std::experimental::basic_string_view<char_type>(str(), size());
|
||||
}
|
||||
};
|
||||
template <typename char_type, typename Size, Size max_size>
|
||||
constexpr Size basic_sstring<char_type, Size, max_size>::npos;
|
||||
|
||||
template <typename char_type, typename size_type, size_type Max, size_type N>
|
||||
inline
|
||||
basic_sstring<char_type, size_type, Max>
|
||||
operator+(const char(&s)[N], const basic_sstring<char_type, size_type, Max>& t) {
|
||||
using sstring = basic_sstring<char_type, size_type, Max>;
|
||||
// don't copy the terminating NUL character
|
||||
sstring ret(typename sstring::initialized_later(), N-1 + t.size());
|
||||
auto p = std::copy(std::begin(s), std::end(s)-1, ret.begin());
|
||||
std::copy(t.begin(), t.end(), p);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
static inline
|
||||
size_t str_len(const char(&s)[N]) { return N - 1; }
|
||||
|
||||
template <size_t N>
|
||||
static inline
|
||||
const char* str_begin(const char(&s)[N]) { return s; }
|
||||
|
||||
template <size_t N>
|
||||
static inline
|
||||
const char* str_end(const char(&s)[N]) { return str_begin(s) + str_len(s); }
|
||||
|
||||
template <typename char_type, typename size_type, size_type max_size>
|
||||
static inline
|
||||
const char_type* str_begin(const basic_sstring<char_type, size_type, max_size>& s) { return s.begin(); }
|
||||
|
||||
template <typename char_type, typename size_type, size_type max_size>
|
||||
static inline
|
||||
const char_type* str_end(const basic_sstring<char_type, size_type, max_size>& s) { return s.end(); }
|
||||
|
||||
template <typename char_type, typename size_type, size_type max_size>
|
||||
static inline
|
||||
size_type str_len(const basic_sstring<char_type, size_type, max_size>& s) { return s.size(); }
|
||||
|
||||
template <typename First, typename Second, typename... Tail>
|
||||
static inline
|
||||
const size_t str_len(const First& first, const Second& second, const Tail&... tail) {
|
||||
return str_len(first) + str_len(second, tail...);
|
||||
}
|
||||
|
||||
template <typename char_type, typename size_type, size_type max_size>
|
||||
inline
|
||||
void swap(basic_sstring<char_type, size_type, max_size>& x,
|
||||
basic_sstring<char_type, size_type, max_size>& y) noexcept
|
||||
{
|
||||
return x.swap(y);
|
||||
}
|
||||
|
||||
template <typename char_type, typename size_type, size_type max_size, typename char_traits>
|
||||
inline
|
||||
std::basic_ostream<char_type, char_traits>&
|
||||
operator<<(std::basic_ostream<char_type, char_traits>& os,
|
||||
const basic_sstring<char_type, size_type, max_size>& s) {
|
||||
return os.write(s.begin(), s.size());
|
||||
}
|
||||
|
||||
template <typename char_type, typename size_type, size_type max_size, typename char_traits>
|
||||
inline
|
||||
std::basic_istream<char_type, char_traits>&
|
||||
operator>>(std::basic_istream<char_type, char_traits>& is,
|
||||
basic_sstring<char_type, size_type, max_size>& s) {
|
||||
std::string tmp;
|
||||
is >> tmp;
|
||||
s = tmp;
|
||||
return is;
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
||||
template <typename char_type, typename size_type, size_type max_size>
|
||||
struct hash<basic_sstring<char_type, size_type, max_size>> {
|
||||
size_t operator()(const basic_sstring<char_type, size_type, max_size>& s) const {
|
||||
return std::hash<std::experimental::basic_string_view<char_type>>()(s);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using sstring = basic_sstring<char, uint32_t, 15>;
|
||||
|
||||
static inline
|
||||
char* copy_str_to(char* dst) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
template <typename Head, typename... Tail>
|
||||
static inline
|
||||
char* copy_str_to(char* dst, const Head& head, const Tail&... tail) {
|
||||
return copy_str_to(std::copy(str_begin(head), str_end(head), dst), tail...);
|
||||
}
|
||||
|
||||
template <typename String = sstring, typename... Args>
|
||||
static String make_sstring(Args&&... args)
|
||||
{
|
||||
String ret(sstring::initialized_later(), str_len(args...));
|
||||
copy_str_to(ret.begin(), args...);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename string_type, typename T>
|
||||
inline
|
||||
string_type to_sstring_sprintf(T value, const char* fmt) {
|
||||
char tmp[sizeof(value) * 3 + 3];
|
||||
auto len = std::sprintf(tmp, fmt, value);
|
||||
using char_type = typename string_type::value_type;
|
||||
return string_type(reinterpret_cast<char_type*>(tmp), len);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
sstring
|
||||
to_sstring_sprintf(T value, const char* fmt) {
|
||||
return to_sstring_sprintf<sstring>(value, fmt);
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
inline
|
||||
string_type to_sstring(int value, void* = nullptr) {
|
||||
return to_sstring_sprintf<string_type>(value, "%d");
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
inline
|
||||
string_type to_sstring(unsigned value, void* = nullptr) {
|
||||
return to_sstring_sprintf<string_type>(value, "%u");
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
inline
|
||||
string_type to_sstring(long value, void* = nullptr) {
|
||||
return to_sstring_sprintf<string_type>(value, "%ld");
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
inline
|
||||
string_type to_sstring(unsigned long value, void* = nullptr) {
|
||||
return to_sstring_sprintf<string_type>(value, "%lu");
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
inline
|
||||
string_type to_sstring(long long value, void* = nullptr) {
|
||||
return to_sstring_sprintf<string_type>(value, "%lld");
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
inline
|
||||
string_type to_sstring(unsigned long long value, void* = nullptr) {
|
||||
return to_sstring_sprintf<string_type>(value, "%llu");
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
inline
|
||||
string_type to_sstring(float value, void* = nullptr) {
|
||||
return to_sstring_sprintf<string_type>(value, "%f");
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
inline
|
||||
string_type to_sstring(double value, void* = nullptr) {
|
||||
return to_sstring_sprintf<string_type>(value, "%f");
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
inline
|
||||
string_type to_sstring(long double value, void* = nullptr) {
|
||||
return to_sstring_sprintf<string_type>(value, "%Lf");
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
inline
|
||||
string_type to_sstring(const char* value, void* = nullptr) {
|
||||
return string_type(value);
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
inline
|
||||
string_type to_sstring(sstring value, void* = nullptr) {
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename string_type = sstring>
|
||||
static string_type to_sstring(const temporary_buffer<char>& buf) {
|
||||
return string_type(buf.get(), buf.size());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
|
||||
bool first = true;
|
||||
os << "{";
|
||||
for (auto&& elem : v) {
|
||||
if (!first) {
|
||||
os << ", ";
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
os << elem;
|
||||
}
|
||||
os << "}";
|
||||
return os;
|
||||
}
|
||||
|
||||
#endif /* SSTRING_HH_ */
|
||||
226
core/stream.hh
226
core/stream.hh
@@ -1,226 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef STREAM_HH_
|
||||
#define STREAM_HH_
|
||||
|
||||
#include "future.hh"
|
||||
#include <exception>
|
||||
#include <cassert>
|
||||
|
||||
// A stream/subscription pair is similar to a promise/future pair,
|
||||
// but apply to a sequence of values instead of a single value.
|
||||
//
|
||||
// A stream<> is the producer side. It may call produce() as long
|
||||
// as the future<> returned from the previous invocation is ready.
|
||||
// To signify no more data is available, call close().
|
||||
//
|
||||
// A subscription<> is the consumer side. It is created by a call
|
||||
// to stream::listen(). Calling subscription::start(),
|
||||
// which registers the data processing callback, starts processing
|
||||
// events. It may register for end-of-stream notifications by
|
||||
// chaining the when_done() future, which also delivers error
|
||||
// events (as exceptions).
|
||||
//
|
||||
// The consumer can pause generation of new data by returning
|
||||
// a non-ready future; when the future becomes ready, the producer
|
||||
// will resume processing.
|
||||
|
||||
template <typename... T>
|
||||
class stream;
|
||||
|
||||
template <typename... T>
|
||||
class subscription;
|
||||
|
||||
template <typename... T>
|
||||
class stream {
|
||||
subscription<T...>* _sub = nullptr;
|
||||
promise<> _done;
|
||||
promise<> _ready;
|
||||
public:
|
||||
using next_fn = std::function<future<> (T...)>;
|
||||
stream() = default;
|
||||
stream(const stream&) = delete;
|
||||
stream(stream&&) = delete;
|
||||
~stream();
|
||||
void operator=(const stream&) = delete;
|
||||
void operator=(stream&&) = delete;
|
||||
|
||||
// Returns a subscription that reads value from this
|
||||
// stream.
|
||||
subscription<T...> listen();
|
||||
|
||||
// Returns a subscription that reads value from this
|
||||
// stream, and also sets up the listen function.
|
||||
subscription<T...> listen(next_fn next);
|
||||
|
||||
// Becomes ready when the listener is ready to accept
|
||||
// values. Call only once, when beginning to produce
|
||||
// values.
|
||||
future<> started();
|
||||
|
||||
// Produce a value. Call only after started(), and after
|
||||
// a previous produce() is ready.
|
||||
future<> produce(T... data);
|
||||
|
||||
// End the stream. Call only after started(), and after
|
||||
// a previous produce() is ready. No functions may be called
|
||||
// after this.
|
||||
void close();
|
||||
|
||||
// Signal an error. Call only after started(), and after
|
||||
// a previous produce() is ready. No functions may be called
|
||||
// after this.
|
||||
template <typename E>
|
||||
void set_exception(E ex);
|
||||
private:
|
||||
void pause(future<> can_continue);
|
||||
void start();
|
||||
friend class subscription<T...>;
|
||||
};
|
||||
|
||||
template <typename... T>
|
||||
class subscription {
|
||||
public:
|
||||
using next_fn = typename stream<T...>::next_fn;
|
||||
private:
|
||||
stream<T...>* _stream;
|
||||
next_fn _next;
|
||||
private:
|
||||
explicit subscription(stream<T...>* s);
|
||||
public:
|
||||
subscription(subscription&& x);
|
||||
~subscription();
|
||||
|
||||
/// \brief Start receiving events from the stream.
|
||||
///
|
||||
/// \param next Callback to call for each event
|
||||
void start(std::function<future<> (T...)> next);
|
||||
|
||||
// Becomes ready when the stream is empty, or when an error
|
||||
// happens (in that case, an exception is held).
|
||||
future<> done();
|
||||
|
||||
friend class stream<T...>;
|
||||
};
|
||||
|
||||
|
||||
template <typename... T>
|
||||
inline
|
||||
stream<T...>::~stream() {
|
||||
if (_sub) {
|
||||
_sub->_stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline
|
||||
subscription<T...>
|
||||
stream<T...>::listen() {
|
||||
return subscription<T...>(this);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline
|
||||
subscription<T...>
|
||||
stream<T...>::listen(next_fn next) {
|
||||
auto sub = subscription<T...>(this);
|
||||
sub.start(std::move(next));
|
||||
return sub;
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline
|
||||
future<>
|
||||
stream<T...>::started() {
|
||||
return _ready.get_future();
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline
|
||||
future<>
|
||||
stream<T...>::produce(T... data) {
|
||||
try {
|
||||
return _sub->_next(std::move(data)...);
|
||||
} catch (...) {
|
||||
_done.set_exception(std::current_exception());
|
||||
// FIXME: tell the producer to stop producing
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline
|
||||
void
|
||||
stream<T...>::close() {
|
||||
_done.set_value();
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
template <typename E>
|
||||
inline
|
||||
void
|
||||
stream<T...>::set_exception(E ex) {
|
||||
_sub->_done.set_exception(ex);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline
|
||||
subscription<T...>::subscription(stream<T...>* s)
|
||||
: _stream(s) {
|
||||
assert(!_stream->_sub);
|
||||
_stream->_sub = this;
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline
|
||||
void
|
||||
subscription<T...>::start(std::function<future<> (T...)> next) {
|
||||
_next = std::move(next);
|
||||
_stream->_ready.set_value();
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline
|
||||
subscription<T...>::~subscription() {
|
||||
if (_stream) {
|
||||
_stream->_sub = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline
|
||||
subscription<T...>::subscription(subscription&& x)
|
||||
: _stream(x._stream), _next(std::move(x._next)) {
|
||||
x._stream = nullptr;
|
||||
if (_stream) {
|
||||
_stream->_sub = this;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline
|
||||
future<>
|
||||
subscription<T...>::done() {
|
||||
return _stream->_done.get_future();
|
||||
}
|
||||
|
||||
#endif /* STREAM_HH_ */
|
||||
48
core/task.hh
48
core/task.hh
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
class task {
|
||||
public:
|
||||
virtual ~task() noexcept {}
|
||||
virtual void run() noexcept = 0;
|
||||
};
|
||||
|
||||
void schedule(std::unique_ptr<task> t);
|
||||
|
||||
template <typename Func>
|
||||
class lambda_task final : public task {
|
||||
Func _func;
|
||||
public:
|
||||
lambda_task(const Func& func) : _func(func) {}
|
||||
lambda_task(Func&& func) : _func(std::move(func)) {}
|
||||
virtual void run() noexcept override { _func(); }
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
inline
|
||||
std::unique_ptr<task>
|
||||
make_task(Func&& func) {
|
||||
return std::make_unique<lambda_task<Func>>(std::forward<Func>(func));
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef TEMPORARY_BUFFER_HH_
|
||||
#define TEMPORARY_BUFFER_HH_
|
||||
|
||||
#include "deleter.hh"
|
||||
#include "util/eclipse.hh"
|
||||
#include <malloc.h>
|
||||
|
||||
/// \addtogroup memory-module
|
||||
/// @{
|
||||
|
||||
/// Temporary, self-managed byte buffer.
|
||||
///
|
||||
/// A \c temporary_buffer is similar to an \c std::string or a \c std::unique_ptr<char[]>,
|
||||
/// but provides more flexible memory management. A \c temporary_buffer can own the memory
|
||||
/// it points to, or it can be shared with another \c temporary_buffer, or point at a substring
|
||||
/// of a buffer. It uses a \ref deleter to manage its memory.
|
||||
///
|
||||
/// A \c temporary_buffer should not be held indefinitely. It can be held while a request
|
||||
/// is processed, or for a similar duration, but not longer, as it can tie up more memory
|
||||
/// that its size indicates.
|
||||
///
|
||||
/// A buffer can be shared: two \c temporary_buffer objects will point to the same data,
|
||||
/// or a subset of it. See the \ref temporary_buffer::share() method.
|
||||
///
|
||||
/// Unless you created a \c temporary_buffer yourself, do not modify its contents, as they
|
||||
/// may be shared with another user that does not expect the data to change.
|
||||
///
|
||||
/// Use cases for a \c temporary_buffer include:
|
||||
/// - passing a substring of a tcp packet for the user to consume (zero-copy
|
||||
/// tcp input)
|
||||
/// - passing a refcounted blob held in memory to tcp, ensuring that when the TCP ACK
|
||||
/// is received, the blob is released (by decrementing its reference count) (zero-copy
|
||||
/// tcp output)
|
||||
///
|
||||
/// \tparam CharType underlying character type (must be a variant of \c char).
|
||||
template <typename CharType>
|
||||
class temporary_buffer {
|
||||
static_assert(sizeof(CharType) == 1, "must buffer stream of bytes");
|
||||
CharType* _buffer;
|
||||
size_t _size;
|
||||
deleter _deleter;
|
||||
public:
|
||||
/// Creates a \c temporary_buffer of a specified size. The buffer is not shared
|
||||
/// with anyone, and is not initialized.
|
||||
///
|
||||
/// \param size buffer size, in bytes
|
||||
explicit temporary_buffer(size_t size)
|
||||
: _buffer(static_cast<CharType*>(malloc(size * sizeof(CharType)))), _size(size)
|
||||
, _deleter(make_free_deleter(_buffer)) {
|
||||
if (size && !_buffer) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
}
|
||||
//explicit temporary_buffer(CharType* borrow, size_t size) : _buffer(borrow), _size(size) {}
|
||||
/// Creates an empty \c temporary_buffer that does not point at anything.
|
||||
temporary_buffer()
|
||||
: _buffer(nullptr)
|
||||
, _size(0) {}
|
||||
temporary_buffer(const temporary_buffer&) = delete;
|
||||
/// Moves a \c temporary_buffer.
|
||||
temporary_buffer(temporary_buffer&& x) : _buffer(x._buffer), _size(x._size), _deleter(std::move(x._deleter)) {
|
||||
x._buffer = nullptr;
|
||||
x._size = 0;
|
||||
}
|
||||
/// Creates a \c temporary_buffer with a specific deleter.
|
||||
///
|
||||
/// \param buf beginning of the buffer held by this \c temporary_buffer
|
||||
/// \param size size of the buffer
|
||||
/// \param d deleter controlling destruction of the buffer. The deleter
|
||||
/// will be destroyed when there are no longer any users for the buffer.
|
||||
temporary_buffer(CharType* buf, size_t size, deleter d)
|
||||
: _buffer(buf), _size(size), _deleter(std::move(d)) {}
|
||||
void operator=(const temporary_buffer&) = delete;
|
||||
/// Moves a \c temporary_buffer.
|
||||
temporary_buffer& operator=(temporary_buffer&& x) {
|
||||
if (this != &x) {
|
||||
_buffer = x._buffer;
|
||||
_size = x._size;
|
||||
_deleter = std::move(x._deleter);
|
||||
x._buffer = nullptr;
|
||||
x._size = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
/// Gets a pointer to the beginning of the buffer.
|
||||
const CharType* get() const { return _buffer; }
|
||||
/// Gets a writable pointer to the beginning of the buffer. Use only
|
||||
/// when you are certain no user expects the buffer data not to change.
|
||||
CharType* get_write() { return _buffer; }
|
||||
/// Gets the buffer size.
|
||||
size_t size() const { return _size; }
|
||||
/// Gets a pointer to the beginning of the buffer.
|
||||
const CharType* begin() { return _buffer; }
|
||||
/// Gets a pointer to the end of the buffer.
|
||||
const CharType* end() { return _buffer + _size; }
|
||||
/// Returns the buffer, but with a reduced size. The original
|
||||
/// buffer is consumed by this call and can no longer be used.
|
||||
///
|
||||
/// \param size New size; must be smaller than current size.
|
||||
/// \return the same buffer, with a prefix removed.
|
||||
temporary_buffer prefix(size_t size) && {
|
||||
auto ret = std::move(*this);
|
||||
ret._size = size;
|
||||
return ret;
|
||||
}
|
||||
/// Reads a character from a specific position in the buffer.
|
||||
///
|
||||
/// \param pos position to read character from; must be less than size.
|
||||
CharType operator[](size_t pos) const {
|
||||
return _buffer[pos];
|
||||
}
|
||||
/// Checks whether the buffer is empty.
|
||||
bool empty() const { return !size(); }
|
||||
/// Checks whether the buffer is not empty.
|
||||
operator bool() { return size(); }
|
||||
/// Create a new \c temporary_buffer object referring to the same
|
||||
/// underlying data. The underlying \ref deleter will not be destroyed
|
||||
/// until both the original and the clone have been destroyed.
|
||||
///
|
||||
/// \return a clone of the buffer object.
|
||||
temporary_buffer share() {
|
||||
return temporary_buffer(_buffer, _size, _deleter.share());
|
||||
}
|
||||
/// Create a new \c temporary_buffer object referring to a substring of the
|
||||
/// same underlying data. The underlying \ref deleter will not be destroyed
|
||||
/// until both the original and the clone have been destroyed.
|
||||
///
|
||||
/// \param pos Position of the first character to share.
|
||||
/// \param len Length of substring to share.
|
||||
/// \return a clone of the buffer object, referring to a substring.
|
||||
temporary_buffer share(size_t pos, size_t len) {
|
||||
auto ret = share();
|
||||
ret._buffer += pos;
|
||||
ret._size = len;
|
||||
return ret;
|
||||
}
|
||||
/// Remove a prefix from the buffer. The underlying data
|
||||
/// is not modified.
|
||||
///
|
||||
/// \param pos Position of first character to retain.
|
||||
void trim_front(size_t pos) {
|
||||
_buffer += pos;
|
||||
_size -= pos;
|
||||
}
|
||||
/// Remove a suffix from the buffer. The underlying data
|
||||
/// is not modified.
|
||||
///
|
||||
/// \param pos Position of first character to drop.
|
||||
void trim(size_t pos) {
|
||||
_size = pos;
|
||||
}
|
||||
/// Stops automatic memory management. When the \c temporary_buffer
|
||||
/// object is destroyed, the underlying \ref deleter will not be called.
|
||||
/// Instead, it is the caller's responsibility to destroy the deleter object
|
||||
/// when the data is no longer needed.
|
||||
///
|
||||
/// \return \ref deleter object managing the data's lifetime.
|
||||
deleter release() {
|
||||
return std::move(_deleter);
|
||||
}
|
||||
/// Creates a \c temporary_buffer object with a specified size, with
|
||||
/// memory aligned to a specific boundary.
|
||||
///
|
||||
/// \param alignment Required alignment; must be a power of two.
|
||||
/// \param size Required size.
|
||||
/// \return a new \c temporary_buffer object.
|
||||
static temporary_buffer aligned(size_t alignment, size_t size) {
|
||||
void *ptr = nullptr;
|
||||
auto ret = ::posix_memalign(&ptr, alignment, size * sizeof(CharType));
|
||||
auto buf = static_cast<CharType*>(ptr);
|
||||
if (ret) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
return temporary_buffer(buf, size, make_free_deleter(buf));
|
||||
}
|
||||
};
|
||||
|
||||
/// @}
|
||||
|
||||
#endif /* TEMPORARY_BUFFER_HH_ */
|
||||
123
core/thread.cc
123
core/thread.cc
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#include "thread.hh"
|
||||
#include "posix.hh"
|
||||
#include <ucontext.h>
|
||||
|
||||
/// \cond internal
|
||||
|
||||
namespace seastar {
|
||||
|
||||
thread_local jmp_buf_link g_unthreaded_context;
|
||||
thread_local jmp_buf_link* g_current_context;
|
||||
|
||||
thread_context::thread_context(std::function<void ()> func)
|
||||
: _func(std::move(func)) {
|
||||
setup();
|
||||
}
|
||||
|
||||
void
|
||||
thread_context::setup() {
|
||||
// use setcontext() for the initial jump, as it allows us
|
||||
// to set up a stack, but continue with longjmp() as it's
|
||||
// much faster.
|
||||
ucontext_t initial_context;
|
||||
auto q = uint64_t(reinterpret_cast<uintptr_t>(this));
|
||||
auto main = reinterpret_cast<void (*)()>(&thread_context::s_main);
|
||||
auto r = getcontext(&initial_context);
|
||||
throw_system_error_on(r == -1);
|
||||
initial_context.uc_stack.ss_sp = _stack.get();
|
||||
initial_context.uc_stack.ss_size = _stack_size;
|
||||
initial_context.uc_link = nullptr;
|
||||
makecontext(&initial_context, main, 2, int(q), int(q >> 32));
|
||||
auto prev = g_current_context;
|
||||
_context.link = prev;
|
||||
_context.thread = this;
|
||||
g_current_context = &_context;
|
||||
if (setjmp(prev->jmpbuf) == 0) {
|
||||
setcontext(&initial_context);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
thread_context::switch_in() {
|
||||
auto prev = g_current_context;
|
||||
g_current_context = &_context;
|
||||
_context.link = prev;
|
||||
if (setjmp(prev->jmpbuf) == 0) {
|
||||
longjmp(_context.jmpbuf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
thread_context::switch_out() {
|
||||
g_current_context = _context.link;
|
||||
if (setjmp(_context.jmpbuf) == 0) {
|
||||
longjmp(g_current_context->jmpbuf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
thread_context::s_main(unsigned int lo, unsigned int hi) {
|
||||
uintptr_t q = lo | (uint64_t(hi) << 32);
|
||||
reinterpret_cast<thread_context*>(q)->main();
|
||||
}
|
||||
|
||||
void
|
||||
thread_context::main() {
|
||||
try {
|
||||
_func();
|
||||
_done.set_value();
|
||||
} catch (...) {
|
||||
_done.set_exception(std::current_exception());
|
||||
}
|
||||
g_current_context = _context.link;
|
||||
longjmp(g_current_context->jmpbuf, 1);
|
||||
}
|
||||
|
||||
namespace thread_impl {
|
||||
|
||||
thread_context* get() {
|
||||
return g_current_context->thread;
|
||||
}
|
||||
|
||||
void switch_in(thread_context* to) {
|
||||
to->switch_in();
|
||||
}
|
||||
|
||||
void switch_out(thread_context* from) {
|
||||
from->switch_out();
|
||||
}
|
||||
|
||||
void init() {
|
||||
g_unthreaded_context.link = nullptr;
|
||||
g_unthreaded_context.thread = nullptr;
|
||||
g_current_context = &g_unthreaded_context;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// \endcond
|
||||
207
core/thread.hh
207
core/thread.hh
@@ -1,207 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "future.hh"
|
||||
#include "do_with.hh"
|
||||
#include "future-util.hh"
|
||||
#include <memory>
|
||||
#include <setjmp.h>
|
||||
#include <type_traits>
|
||||
|
||||
/// \defgroup thread-module Seastar threads
|
||||
///
|
||||
/// Seastar threads provide an execution environment where blocking
|
||||
/// is tolerated; you can issue I/O, and wait for it in the same function,
|
||||
/// rather then establishing a callback to be called with \ref future<>::then().
|
||||
///
|
||||
/// Seastar threads are not the same as operating system threads:
|
||||
/// - seastar threads are cooperative; they are never preempted except
|
||||
/// at blocking points (see below)
|
||||
/// - seastar threads always run on the same core they were launched on
|
||||
///
|
||||
/// Like other seastar code, seastar threads may not issue blocking system calls.
|
||||
///
|
||||
/// A seastar thread blocking point is any function that returns a \ref future<>.
|
||||
/// you block by calling \ref future<>::get(); this waits for the future to become
|
||||
/// available, and in the meanwhile, other seastar threads and seastar non-threaded
|
||||
/// code may execute.
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// seastar::thread th([] {
|
||||
/// sleep(5s).get(); // blocking point
|
||||
/// });
|
||||
/// \endcode
|
||||
///
|
||||
/// An easy way to launch a thread and carry out some computation, and return a
|
||||
/// result from this execution is by using the \ref seastar::async() function.
|
||||
/// The result is returned as a future, so that non-threaded code can wait for
|
||||
/// the thread to terminate and yield a result.
|
||||
|
||||
/// Seastar API namespace
|
||||
namespace seastar {
|
||||
|
||||
/// \addtogroup thread-module
|
||||
/// @{
|
||||
|
||||
class thread;
|
||||
|
||||
/// \cond internal
|
||||
class thread_context;
|
||||
|
||||
namespace thread_impl {
|
||||
|
||||
thread_context* get();
|
||||
void switch_in(thread_context* to);
|
||||
void switch_out(thread_context* from);
|
||||
void init();
|
||||
|
||||
}
|
||||
|
||||
struct jmp_buf_link {
|
||||
jmp_buf jmpbuf;
|
||||
jmp_buf_link* link;
|
||||
thread_context* thread;
|
||||
};
|
||||
|
||||
extern thread_local jmp_buf_link g_unthreaded_context;
|
||||
|
||||
// Internal class holding thread state. We can't hold this in
|
||||
// \c thread itself because \c thread is movable, and we want pointers
|
||||
// to this state to be captured.
|
||||
class thread_context {
|
||||
static constexpr size_t _stack_size = 128*1024;
|
||||
std::unique_ptr<char[]> _stack{new char[_stack_size]};
|
||||
std::function<void ()> _func;
|
||||
jmp_buf_link _context;
|
||||
promise<> _done;
|
||||
bool _joined = false;
|
||||
private:
|
||||
static void s_main(unsigned int lo, unsigned int hi);
|
||||
void setup();
|
||||
void main();
|
||||
public:
|
||||
thread_context(std::function<void ()> func);
|
||||
void switch_in();
|
||||
void switch_out();
|
||||
friend class thread;
|
||||
friend void thread_impl::switch_in(thread_context*);
|
||||
friend void thread_impl::switch_out(thread_context*);
|
||||
};
|
||||
|
||||
/// \endcond
|
||||
|
||||
|
||||
/// \brief thread - stateful thread of execution
|
||||
///
|
||||
/// Threads allow using seastar APIs in a blocking manner,
|
||||
/// by calling future::get() on a non-ready future. When
|
||||
/// this happens, the thread is put to sleep until the future
|
||||
/// becomes ready.
|
||||
class thread {
|
||||
std::unique_ptr<thread_context> _context;
|
||||
static thread_local thread* _current;
|
||||
public:
|
||||
/// \brief Constructs a \c thread object that does not represent a thread
|
||||
/// of execution.
|
||||
thread() = default;
|
||||
/// \brief Constructs a \c thread object that represents a thread of execution
|
||||
///
|
||||
/// \param func Callable object to execute in thread. The callable is
|
||||
/// called immediately.
|
||||
template <typename Func>
|
||||
thread(Func func);
|
||||
/// \brief Moves a thread object.
|
||||
thread(thread&& x) noexcept = default;
|
||||
/// \brief Move-assigns a thread object.
|
||||
thread& operator=(thread&& x) noexcept = default;
|
||||
/// \brief Destroys a \c thread object.
|
||||
///
|
||||
/// The thread must not represent a running thread of execution (see join()).
|
||||
~thread() { assert(!_context || _context->_joined); }
|
||||
/// \brief Waits for thread execution to terminate.
|
||||
///
|
||||
/// Waits for thread execution to terminate, and marks the thread object as not
|
||||
/// representing a running thread of execution.
|
||||
future<> join();
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
inline
|
||||
thread::thread(Func func)
|
||||
: _context(std::make_unique<thread_context>(func)) {
|
||||
}
|
||||
|
||||
inline
|
||||
future<>
|
||||
thread::join() {
|
||||
_context->_joined = true;
|
||||
return _context->_done.get_future();
|
||||
}
|
||||
|
||||
/// Executes a callable in a seastar thread.
|
||||
///
|
||||
/// Runs a block of code in a threaded context,
|
||||
/// which allows it to block (using \ref future::get()). The
|
||||
/// result of the callable is returned as a future.
|
||||
///
|
||||
/// \param func a callable to be executed in a thread
|
||||
/// \param args a parameter pack to be forwarded to \c func.
|
||||
/// \return whatever \c func returns, as a future.
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// future<int> compute_sum(int a, int b) {
|
||||
/// return seastar::async([a, b] {
|
||||
/// // some blocking code:
|
||||
/// sleep(1s).get();
|
||||
/// return a + b;
|
||||
/// });
|
||||
/// }
|
||||
/// \endcode
|
||||
template <typename Func, typename... Args>
|
||||
inline
|
||||
futurize_t<std::result_of_t<std::decay_t<Func>(std::decay_t<Args>...)>>
|
||||
async(Func&& func, Args&&... args) {
|
||||
using return_type = std::result_of_t<std::decay_t<Func>(std::decay_t<Args>...)>;
|
||||
struct work {
|
||||
Func func;
|
||||
std::tuple<Args...> args;
|
||||
promise<return_type> pr;
|
||||
thread th;
|
||||
};
|
||||
return do_with(work{std::forward<Func>(func), std::forward_as_tuple(std::forward<Args>(args)...)}, [] (work& w) {
|
||||
auto ret = w.pr.get_future();
|
||||
w.th = thread([&w] {
|
||||
futurize<return_type>::apply(std::move(w.func), std::move(w.args)).forward_to(std::move(w.pr));
|
||||
});
|
||||
return w.th.join().then([ret = std::move(ret)] () mutable {
|
||||
return std::move(ret);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Imported from OSv:
|
||||
*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*
|
||||
* This work is open source software, licensed under the terms of the
|
||||
* BSD license as described in the LICENSE file in the top-level directory.
|
||||
*/
|
||||
|
||||
#ifndef __TIMER_SET_HH
|
||||
#define __TIMER_SET_HH
|
||||
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <bitset>
|
||||
#include <array>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include "bitset-iter.hh"
|
||||
|
||||
namespace bi = boost::intrusive;
|
||||
|
||||
namespace seastar {
|
||||
/**
|
||||
* A data structure designed for holding and expiring timers. It's
|
||||
* optimized for timer non-delivery by deferring sorting cost until
|
||||
* expiry time. The optimization is based on the observation that in
|
||||
* many workloads timers are cancelled or rescheduled before they
|
||||
* expire. That's especially the case for TCP timers.
|
||||
*
|
||||
* The template type "Timer" should have a method named
|
||||
* get_timeout() which returns Timer::time_point which denotes
|
||||
* timer's expiration.
|
||||
*/
|
||||
template<typename Timer, bi::list_member_hook<> Timer::*link>
|
||||
class timer_set {
|
||||
public:
|
||||
using time_point = typename Timer::time_point;
|
||||
using timer_list_t = bi::list<Timer, bi::member_hook<Timer, bi::list_member_hook<>, link>>;
|
||||
private:
|
||||
using duration = typename Timer::duration;
|
||||
using timestamp_t = typename Timer::duration::rep;
|
||||
|
||||
static constexpr timestamp_t max_timestamp = std::numeric_limits<timestamp_t>::max();
|
||||
static constexpr int timestamp_bits = std::numeric_limits<timestamp_t>::digits;
|
||||
|
||||
// The last bucket is reserved for active timers with timeout <= _last.
|
||||
static constexpr int n_buckets = timestamp_bits + 1;
|
||||
|
||||
std::array<timer_list_t, n_buckets> _buckets;
|
||||
timestamp_t _last;
|
||||
timestamp_t _next;
|
||||
|
||||
std::bitset<n_buckets> _non_empty_buckets;
|
||||
private:
|
||||
static timestamp_t get_timestamp(time_point _time_point)
|
||||
{
|
||||
return _time_point.time_since_epoch().count();
|
||||
}
|
||||
|
||||
static timestamp_t get_timestamp(Timer& timer)
|
||||
{
|
||||
return get_timestamp(timer.get_timeout());
|
||||
}
|
||||
|
||||
int get_index(timestamp_t timestamp) const
|
||||
{
|
||||
if (timestamp <= _last) {
|
||||
return n_buckets - 1;
|
||||
}
|
||||
|
||||
auto index = bitsets::count_leading_zeros(timestamp ^ _last);
|
||||
assert(index < n_buckets - 1);
|
||||
return index;
|
||||
}
|
||||
|
||||
int get_index(Timer& timer) const
|
||||
{
|
||||
return get_index(get_timestamp(timer));
|
||||
}
|
||||
|
||||
int get_last_non_empty_bucket() const
|
||||
{
|
||||
return bitsets::get_last_set(_non_empty_buckets);
|
||||
}
|
||||
public:
|
||||
timer_set()
|
||||
: _last(0)
|
||||
, _next(max_timestamp)
|
||||
, _non_empty_buckets(0)
|
||||
{
|
||||
}
|
||||
|
||||
~timer_set() {
|
||||
for (auto&& list : _buckets) {
|
||||
while (!list.empty()) {
|
||||
auto& timer = *list.begin();
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds timer to the active set.
|
||||
*
|
||||
* The value returned by timer.get_timeout() is used as timer's expiry. The result
|
||||
* of timer.get_timeout() must not change while the timer is in the active set.
|
||||
*
|
||||
* Preconditions:
|
||||
* - this timer must not be currently in the active set or in the expired set.
|
||||
*
|
||||
* Postconditions:
|
||||
* - this timer will be added to the active set until it is expired
|
||||
* by a call to expire() or removed by a call to remove().
|
||||
*
|
||||
* Returns true if and only if this timer's timeout is less than get_next_timeout().
|
||||
* When this function returns true the caller should reschedule expire() to be
|
||||
* called at timer.get_timeout() to ensure timers are expired in a timely manner.
|
||||
*/
|
||||
bool insert(Timer& timer)
|
||||
{
|
||||
auto timestamp = get_timestamp(timer);
|
||||
auto index = get_index(timestamp);
|
||||
|
||||
_buckets[index].push_back(timer);
|
||||
_non_empty_buckets[index] = true;
|
||||
|
||||
if (timestamp < _next) {
|
||||
_next = timestamp;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes timer from the active set.
|
||||
*
|
||||
* Preconditions:
|
||||
* - timer must be currently in the active set. Note: it must not be in
|
||||
* the expired set.
|
||||
*
|
||||
* Postconditions:
|
||||
* - timer is no longer in the active set.
|
||||
* - this object will no longer hold any references to this timer.
|
||||
*/
|
||||
void remove(Timer& timer)
|
||||
{
|
||||
auto index = get_index(timer);
|
||||
auto& list = _buckets[index];
|
||||
list.erase(list.iterator_to(timer));
|
||||
if (list.empty()) {
|
||||
_non_empty_buckets[index] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expires active timers.
|
||||
*
|
||||
* The time points passed to this function must be monotonically increasing.
|
||||
* Use get_next_timeout() to query for the next time point.
|
||||
*
|
||||
* Preconditions:
|
||||
* - the time_point passed to this function must not be lesser than
|
||||
* the previous one passed to this function.
|
||||
*
|
||||
* Postconditons:
|
||||
* - all timers from the active set with Timer::get_timeout() <= now are moved
|
||||
* to the expired set.
|
||||
*/
|
||||
timer_list_t expire(time_point now)
|
||||
{
|
||||
timer_list_t exp;
|
||||
auto timestamp = get_timestamp(now);
|
||||
|
||||
if (timestamp < _last) {
|
||||
abort();
|
||||
}
|
||||
|
||||
auto index = get_index(timestamp);
|
||||
|
||||
for (int i : bitsets::for_each_set(_non_empty_buckets, index + 1)) {
|
||||
exp.splice(exp.end(), _buckets[i]);
|
||||
_non_empty_buckets[i] = false;
|
||||
}
|
||||
|
||||
_last = timestamp;
|
||||
_next = max_timestamp;
|
||||
|
||||
auto& list = _buckets[index];
|
||||
while (!list.empty()) {
|
||||
auto& timer = *list.begin();
|
||||
list.pop_front();
|
||||
if (timer.get_timeout() <= now) {
|
||||
exp.push_back(timer);
|
||||
} else {
|
||||
insert(timer);
|
||||
}
|
||||
}
|
||||
|
||||
_non_empty_buckets[index] = !list.empty();
|
||||
|
||||
if (_next == max_timestamp && _non_empty_buckets.any()) {
|
||||
for (auto& timer : _buckets[get_last_non_empty_bucket()]) {
|
||||
_next = std::min(_next, get_timestamp(timer));
|
||||
}
|
||||
}
|
||||
return exp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a time point at which expire() should be called
|
||||
* in order to ensure timers are expired in a timely manner.
|
||||
*
|
||||
* Returned values are monotonically increasing.
|
||||
*/
|
||||
time_point get_next_timeout() const
|
||||
{
|
||||
return time_point(duration(std::max(_last, _next)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears both active and expired timer sets.
|
||||
*/
|
||||
void clear()
|
||||
{
|
||||
for (int i : bitsets::for_each_set(_non_empty_buckets)) {
|
||||
_buckets[i].clear();
|
||||
}
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
size_t res = 0;
|
||||
for (int i : bitsets::for_each_set(_non_empty_buckets)) {
|
||||
res += _buckets[i].size();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if and only if there are no timers in the active set.
|
||||
*/
|
||||
bool empty() const
|
||||
{
|
||||
return _non_empty_buckets.none();
|
||||
}
|
||||
|
||||
time_point now() {
|
||||
return Timer::clock::now();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <experimental/optional>
|
||||
#include <atomic>
|
||||
#include "future.hh"
|
||||
#include "timer-set.hh"
|
||||
|
||||
using clock_type = std::chrono::high_resolution_clock;
|
||||
|
||||
template <typename Clock = std::chrono::high_resolution_clock>
|
||||
class timer {
|
||||
public:
|
||||
typedef typename Clock::time_point time_point;
|
||||
typedef typename Clock::duration duration;
|
||||
typedef Clock clock;
|
||||
private:
|
||||
using callback_t = std::function<void()>;
|
||||
boost::intrusive::list_member_hook<> _link;
|
||||
callback_t _callback;
|
||||
time_point _expiry;
|
||||
std::experimental::optional<duration> _period;
|
||||
bool _armed = false;
|
||||
bool _queued = false;
|
||||
bool _expired = false;
|
||||
void readd_periodic();
|
||||
void arm_state(time_point until, std::experimental::optional<duration> period);
|
||||
public:
|
||||
timer() = default;
|
||||
timer(timer&& t) noexcept : _callback(std::move(t._callback)), _expiry(std::move(t._expiry)), _period(std::move(t._period)),
|
||||
_armed(t._armed), _queued(t._queued), _expired(t._expired) {
|
||||
_link.swap_nodes(t._link);
|
||||
t._queued = false;
|
||||
t._armed = false;
|
||||
}
|
||||
explicit timer(callback_t&& callback);
|
||||
~timer();
|
||||
future<> expired();
|
||||
void set_callback(callback_t&& callback);
|
||||
void arm(time_point until, std::experimental::optional<duration> period = {});
|
||||
void rearm(time_point until, std::experimental::optional<duration> period = {});
|
||||
void arm(duration delta);
|
||||
void arm_periodic(duration delta);
|
||||
bool armed() const { return _armed; }
|
||||
bool cancel();
|
||||
time_point get_timeout();
|
||||
friend class reactor;
|
||||
friend class seastar::timer_set<timer, &timer::_link>;
|
||||
};
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef TRANSFER_HH_
|
||||
#define TRANSFER_HH_
|
||||
|
||||
// Helper functions for copying or moving multiple objects in an exception
|
||||
// safe manner, then destroying the sources.
|
||||
//
|
||||
// To transfer, call transfer_pass1(allocator, &from, &to) on all object pairs,
|
||||
// (this copies the object from @from to @to). If no exceptions are encountered,
|
||||
// call transfer_pass2(allocator, &from, &to). This destroys the object at the
|
||||
// origin. If exceptions were encountered, simply destroy all copied objects.
|
||||
//
|
||||
// As an optimization, if the objects are moveable without throwing (noexcept)
|
||||
// transfer_pass1() simply moves the objects and destroys the source, and
|
||||
// transfer_pass2() does nothing.
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
void
|
||||
transfer_pass1(Alloc& a, T* from, T* to,
|
||||
typename std::enable_if<std::is_nothrow_move_constructible<T>::value>::type* = nullptr) {
|
||||
a.construct(to, std::move(*from));
|
||||
a.destroy(from);
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
void
|
||||
transfer_pass2(Alloc& a, T* from, T* to,
|
||||
typename std::enable_if<std::is_nothrow_move_constructible<T>::value>::type* = nullptr) {
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
void
|
||||
transfer_pass1(Alloc& a, T* from, T* to,
|
||||
typename std::enable_if<!std::is_nothrow_move_constructible<T>::value>::type* = nullptr) {
|
||||
a.construct(to, *from);
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
inline
|
||||
void
|
||||
transfer_pass2(Alloc& a, T* from, T* to,
|
||||
typename std::enable_if<!std::is_nothrow_move_constructible<T>::value>::type* = nullptr) {
|
||||
a.destroy(from);
|
||||
}
|
||||
|
||||
#endif /* TRANSFER_HH_ */
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// The following unaligned_cast<T*>(p) is a portable replacement for
|
||||
// reinterpret_cast<T*>(p) which should be used every time address p
|
||||
// is not guaranteed to be properly aligned to alignof(T).
|
||||
//
|
||||
// On architectures like x86 and ARM, where unaligned access is allowed,
|
||||
// unaligned_cast will behave the same as reinterpret_cast and will generate
|
||||
// the same code.
|
||||
//
|
||||
// Certain architectures (e.g., MIPS) make it extremely slow or outright
|
||||
// forbidden to use ordinary machine instructions on a primitive type at an
|
||||
// unaligned addresses - e.g., access a uint32_t at an address which is not
|
||||
// a multiple of 4. Gcc's "undefined behavior sanitizer" (enabled in our debug
|
||||
// build) also catches such unaligned accesses and reports them as errors,
|
||||
// even when running on x86.
|
||||
//
|
||||
// Therefore, reinterpret_cast<int32_t*> on an address which is not guaranteed
|
||||
// to be a multiple of 4 may generate extremely slow code or runtime errors,
|
||||
// and must be avoided. The compiler needs to be told about the unaligned
|
||||
// access, so it can generate reasonably-efficient code for the access
|
||||
// (in MIPS, this means generating two instructions "lwl" and "lwr", instead
|
||||
// of the one instruction "lw" which faults on unaligned/ access). The way to
|
||||
// tell the compiler this is with __attribute__((packed)). This will also
|
||||
// cause the sanitizer not to generate runtime alignment checks for this
|
||||
// access.
|
||||
|
||||
template <typename T>
|
||||
struct unaligned {
|
||||
T raw;
|
||||
unaligned() = default;
|
||||
unaligned(T x) : raw(x) {}
|
||||
unaligned& operator=(const T& x) { raw = x; return *this; }
|
||||
operator T() const { return raw; }
|
||||
} __attribute__((packed));
|
||||
|
||||
template <typename T, typename F>
|
||||
inline auto unaligned_cast(F* p) {
|
||||
return reinterpret_cast<unaligned<std::remove_pointer_t<T>>*>(p);
|
||||
}
|
||||
|
||||
template <typename T, typename F>
|
||||
inline auto unaligned_cast(const F* p) {
|
||||
return reinterpret_cast<const unaligned<std::remove_pointer_t<T>>*>(p);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef UNITS_HH_
|
||||
#define UNITS_HH_
|
||||
|
||||
static constexpr size_t KB = 1 << 10;
|
||||
static constexpr size_t MB = 1 << 20;
|
||||
static constexpr size_t GB = 1 << 30;
|
||||
|
||||
#endif
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2014 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef VECTOR_DATA_SINK_HH_
|
||||
#define VECTOR_DATA_SINK_HH_
|
||||
|
||||
#include "core/reactor.hh"
|
||||
|
||||
class vector_data_sink final : public data_sink_impl {
|
||||
public:
|
||||
using vector_type = std::vector<net::packet>;
|
||||
private:
|
||||
vector_type& _v;
|
||||
public:
|
||||
vector_data_sink(vector_type& v) : _v(v) {}
|
||||
|
||||
virtual future<> put(net::packet p) override {
|
||||
_v.push_back(std::move(p));
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
virtual future<> close() override {
|
||||
// TODO: close on local side
|
||||
return make_ready_future<>();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
57
core/vla.hh
57
core/vla.hh
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef VLA_HH_
|
||||
#define VLA_HH_
|
||||
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <assert.h>
|
||||
#include <type_traits>
|
||||
|
||||
// Some C APIs have a structure with a variable length array at the end.
|
||||
// This is a helper function to help allocate it.
|
||||
//
|
||||
// for a structure
|
||||
//
|
||||
// struct xx { int a; float b[0]; };
|
||||
//
|
||||
// use
|
||||
//
|
||||
// make_struct_with_vla(&xx::b, number_of_bs);
|
||||
//
|
||||
// to allocate it.
|
||||
//
|
||||
template <class S, typename E>
|
||||
inline
|
||||
std::unique_ptr<S>
|
||||
make_struct_with_vla(E S::*last, size_t nr) {
|
||||
auto fake = reinterpret_cast<S*>(0);
|
||||
size_t offset = reinterpret_cast<uintptr_t>(&(fake->*last));
|
||||
size_t element_size = sizeof((fake->*last)[0]);
|
||||
assert(offset == sizeof(S));
|
||||
auto p = ::operator new(offset + element_size * nr);
|
||||
return std::unique_ptr<S>(new (p) S());
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif /* VLA_HH_ */
|
||||
@@ -1,219 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <xen/xen.h>
|
||||
#include <xen/event_channel.h> // kernel interface
|
||||
#include <xen/sys/evtchn.h> // userspace interface
|
||||
|
||||
#include "core/reactor.hh"
|
||||
#include "core/semaphore.hh"
|
||||
#include "core/future-util.hh"
|
||||
#include "evtchn.hh"
|
||||
#include "osv_xen.hh"
|
||||
|
||||
namespace xen {
|
||||
|
||||
void evtchn::make_ready_port(int port) {
|
||||
auto ports = _ports.equal_range(port);
|
||||
for (auto i = ports.first; i != ports.second; ++i) {
|
||||
i->second->_sem.signal();
|
||||
}
|
||||
}
|
||||
|
||||
void evtchn::port_moved(int prt, port* old, port* now) {
|
||||
auto ports = _ports.equal_range(prt);
|
||||
for (auto i = ports.first; i != ports.second; ++i) {
|
||||
if (i->second == old) {
|
||||
i->second = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void evtchn::port_deleted(int prt, port* obj) {
|
||||
auto ports = _ports.equal_range(prt);
|
||||
for (auto i = ports.first; i != ports.second; ++i) {
|
||||
if (i->second == obj) {
|
||||
i = _ports.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
port::port(int p)
|
||||
: _port(p), _sem(0), _evtchn(evtchn::instance()) {
|
||||
_evtchn->_ports.emplace(p, this);
|
||||
}
|
||||
|
||||
port::port(port&& other)
|
||||
: _port(other._port), _sem(std::move(other._sem)), _evtchn(other._evtchn) {
|
||||
if (_port != -1) {
|
||||
_evtchn->port_moved(_port, &other, this);
|
||||
}
|
||||
}
|
||||
|
||||
port::~port() {
|
||||
// FIXME: unbind from Xen
|
||||
if (_port != -1) {
|
||||
_evtchn->port_deleted(_port, this);
|
||||
}
|
||||
}
|
||||
|
||||
port& port::operator=(port&& other) {
|
||||
if (this != &other) {
|
||||
this->~port();
|
||||
new (this) port(std::move(other));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
future<> port::pending() {
|
||||
return _sem.wait();
|
||||
}
|
||||
|
||||
void port::notify() {
|
||||
_evtchn->notify(_port);
|
||||
}
|
||||
|
||||
void port::umask() {
|
||||
_evtchn->umask(&_port, 1);
|
||||
}
|
||||
|
||||
class userspace_evtchn: public evtchn {
|
||||
pollable_fd _evtchn;
|
||||
int ports[2];
|
||||
protected:
|
||||
virtual void umask(int *port, unsigned count) override;
|
||||
public:
|
||||
userspace_evtchn(unsigned otherend);
|
||||
virtual port bind() override;
|
||||
virtual void notify(int port) override;
|
||||
};
|
||||
|
||||
userspace_evtchn::userspace_evtchn(unsigned otherend)
|
||||
: evtchn(otherend)
|
||||
, _evtchn(pollable_fd(file_desc::open("/dev/xen/evtchn", O_RDWR | O_NONBLOCK)))
|
||||
{
|
||||
keep_doing([this] {
|
||||
return _evtchn.read_some(reinterpret_cast<char *>(&ports), sizeof(ports)).then([this] (size_t s)
|
||||
{
|
||||
auto count = s / sizeof(ports[0]);
|
||||
for (unsigned i = 0; i < count; ++i) {
|
||||
make_ready_port(ports[i]);
|
||||
}
|
||||
umask(ports, count);
|
||||
return make_ready_future<>();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
port userspace_evtchn::bind()
|
||||
{
|
||||
struct ioctl_evtchn_bind_unbound_port bind = { _otherend };
|
||||
|
||||
auto ret = _evtchn.get_file_desc().ioctl<struct ioctl_evtchn_bind_unbound_port>(IOCTL_EVTCHN_BIND_UNBOUND_PORT, bind);
|
||||
return port(ret);
|
||||
}
|
||||
|
||||
void userspace_evtchn::notify(int port)
|
||||
{
|
||||
struct ioctl_evtchn_notify notify;
|
||||
notify.port = port;
|
||||
|
||||
_evtchn.get_file_desc().ioctl<struct ioctl_evtchn_notify>(IOCTL_EVTCHN_NOTIFY, notify);
|
||||
}
|
||||
|
||||
void userspace_evtchn::umask(int *port, unsigned count)
|
||||
{
|
||||
_evtchn.get_file_desc().write(port, count * sizeof(port));
|
||||
}
|
||||
|
||||
#ifdef HAVE_OSV
|
||||
class kernel_evtchn: public evtchn {
|
||||
// We need to convert extra-seastar events to intra-seastar events
|
||||
// (in this case, the semaphore interface of evtchn). The interface for
|
||||
// that currently is the reactor notifier.
|
||||
std::unique_ptr<reactor_notifier> _notified;
|
||||
static void make_ready(void *arg);
|
||||
void process_interrupts(int port);
|
||||
public:
|
||||
kernel_evtchn(unsigned otherend)
|
||||
: evtchn(otherend)
|
||||
, _notified(engine().make_reactor_notifier()) {}
|
||||
virtual port bind() override;
|
||||
virtual void notify(int port) override;
|
||||
};
|
||||
|
||||
void kernel_evtchn::make_ready(void *arg) {
|
||||
auto notifier = reinterpret_cast<reactor_notifier *>(arg);
|
||||
notifier->signal();
|
||||
}
|
||||
|
||||
port kernel_evtchn::bind() {
|
||||
|
||||
unsigned int irq;
|
||||
|
||||
int newp;
|
||||
irq = bind_listening_port_to_irq(_otherend, &newp);
|
||||
|
||||
port p(newp);
|
||||
|
||||
auto notifier = reinterpret_cast<void *>(_notified.get());
|
||||
|
||||
intr_add_handler("", irq, NULL, make_ready, notifier, 0, 0);
|
||||
unmask_evtchn(newp);
|
||||
process_interrupts(newp);
|
||||
return p;
|
||||
}
|
||||
|
||||
void kernel_evtchn::process_interrupts(int port) {
|
||||
_notified->wait().then([this, port] () {
|
||||
make_ready_port(port);
|
||||
process_interrupts(port);
|
||||
});
|
||||
}
|
||||
|
||||
void kernel_evtchn::notify(int port) {
|
||||
notify_remote_via_evtchn(port);
|
||||
}
|
||||
#endif
|
||||
|
||||
evtchn *evtchn::_instance = nullptr;
|
||||
|
||||
evtchn *evtchn::instance()
|
||||
{
|
||||
if (!_instance) {
|
||||
throw std::runtime_error("Acquiring evtchn instance without specifying otherend: invalid context");
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
|
||||
evtchn *evtchn::instance(bool userspace, unsigned otherend)
|
||||
{
|
||||
if (!_instance) {
|
||||
#ifdef HAVE_OSV
|
||||
if (!userspace) {
|
||||
_instance = new kernel_evtchn(otherend);
|
||||
} else
|
||||
#endif
|
||||
_instance = new userspace_evtchn(otherend);
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
#ifndef _XEN_EVTCHN_HH
|
||||
#define _XEN_EVTCHN_HH
|
||||
|
||||
#include "core/posix.hh"
|
||||
#include "core/future.hh"
|
||||
|
||||
namespace xen {
|
||||
|
||||
class evtchn;
|
||||
|
||||
class port {
|
||||
int _port = -1;
|
||||
semaphore _sem;
|
||||
evtchn *_evtchn;
|
||||
public:
|
||||
explicit port(int p);
|
||||
port() = default;
|
||||
port(port&& other);
|
||||
~port();
|
||||
port& operator=(port&& other);
|
||||
int number() const { return _port; }
|
||||
future<> pending();
|
||||
void notify();
|
||||
void umask();
|
||||
|
||||
friend class evtchn;
|
||||
};
|
||||
|
||||
class evtchn {
|
||||
static evtchn *_instance;
|
||||
protected:
|
||||
unsigned _otherend;
|
||||
void make_ready_port(int port);
|
||||
void port_moved(int prt, port* old, port* now);
|
||||
void port_deleted(int prt, port* old);
|
||||
std::unordered_multimap<int, port*> _ports;
|
||||
virtual void notify(int port) = 0;
|
||||
virtual void umask(int *port, unsigned count) {};
|
||||
friend class port;
|
||||
public:
|
||||
static evtchn *instance(bool userspace, unsigned otherend);
|
||||
static evtchn *instance();
|
||||
evtchn(unsigned otherend) : _otherend(otherend) {}
|
||||
virtual port bind() = 0;
|
||||
port bind(int p) { return port(p); };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,240 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <xen/xen.h>
|
||||
#include <xen/grant_table.h> // Kernel interface
|
||||
#include <xen/sys/gntalloc.h> // Userspace interface
|
||||
#include <atomic>
|
||||
|
||||
#include "osv_xen.hh"
|
||||
#include "gntalloc.hh"
|
||||
|
||||
namespace xen {
|
||||
|
||||
gntref invalid_ref;
|
||||
|
||||
// FIXME: Most of the destructors are yet to be coded
|
||||
//
|
||||
|
||||
class userspace_grant_head : public grant_head {
|
||||
std::atomic<int> _ref_head = { 0 };
|
||||
std::vector<gntref> _refs;
|
||||
public:
|
||||
userspace_grant_head(std::vector<gntref> v) : _refs(v) {}
|
||||
virtual gntref new_ref() override;
|
||||
virtual gntref new_ref(void *addr, size_t size) override;
|
||||
virtual void free_ref(gntref& ref);
|
||||
};
|
||||
|
||||
class userspace_gntalloc : public gntalloc {
|
||||
file_desc _gntalloc;
|
||||
mmap_area _gntmap;
|
||||
struct ioctl_gntalloc_alloc_gref *get_gref(unsigned nr_ents);
|
||||
|
||||
public:
|
||||
explicit userspace_gntalloc(unsigned otherend);
|
||||
~userspace_gntalloc();
|
||||
virtual gntref alloc_ref() override;
|
||||
virtual grant_head *alloc_ref(unsigned refs) override;
|
||||
};
|
||||
|
||||
userspace_gntalloc::userspace_gntalloc(unsigned otherend)
|
||||
: gntalloc(otherend)
|
||||
, _gntalloc(file_desc::open("/dev/xen/gntalloc", O_RDWR)) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct ioctl_gntalloc_alloc_gref*
|
||||
userspace_gntalloc::get_gref(unsigned nr_ents)
|
||||
{
|
||||
struct ioctl_gntalloc_alloc_gref *gref;
|
||||
gref = static_cast<struct ioctl_gntalloc_alloc_gref *>(malloc(sizeof(*gref) + nr_ents * sizeof(gref->gref_ids[0])));
|
||||
gref->domid = _otherend;
|
||||
gref->flags = GNTALLOC_FLAG_WRITABLE;
|
||||
gref->count = nr_ents;
|
||||
_gntalloc.ioctl<struct ioctl_gntalloc_alloc_gref>(IOCTL_GNTALLOC_ALLOC_GREF, *gref);
|
||||
|
||||
return gref;
|
||||
}
|
||||
|
||||
grant_head *userspace_gntalloc::alloc_ref(unsigned nr_ents) {
|
||||
|
||||
auto gref = get_gref(nr_ents);
|
||||
|
||||
_gntmap = _gntalloc.map_shared_rw(4096 * nr_ents, gref->index);
|
||||
char *addr = _gntmap.get();
|
||||
|
||||
std::vector<gntref> v;
|
||||
for (unsigned i = 0; i < nr_ents; ++i) {
|
||||
auto ref = gref->gref_ids[i];
|
||||
auto *page = addr + 4096 * i;
|
||||
v.push_back({int(ref), page});
|
||||
}
|
||||
|
||||
free(gref);
|
||||
return new userspace_grant_head(v);
|
||||
}
|
||||
|
||||
gntref userspace_gntalloc::alloc_ref() {
|
||||
|
||||
auto gref = get_gref(1);
|
||||
|
||||
// FIXME: unmap?
|
||||
void *addr = _gntalloc.map_shared_rw(4096, gref->index).release();
|
||||
auto p = gntref{int(gref->gref_ids[0]), addr};
|
||||
free(gref);
|
||||
return p;
|
||||
}
|
||||
|
||||
gntref userspace_grant_head::new_ref() {
|
||||
auto r = _refs.back();
|
||||
_refs.pop_back();
|
||||
return r;
|
||||
}
|
||||
|
||||
gntref userspace_grant_head::new_ref(void *addr, size_t size) {
|
||||
auto ref = _refs.back();
|
||||
memcpy(ref.page, addr, size);
|
||||
_refs.pop_back();
|
||||
return ref;
|
||||
}
|
||||
|
||||
void userspace_grant_head::free_ref(gntref& ref) {
|
||||
_refs.push_back(ref);
|
||||
ref = invalid_ref;
|
||||
}
|
||||
|
||||
#ifdef HAVE_OSV
|
||||
class kernel_gntalloc;
|
||||
|
||||
class kernel_grant_head : public grant_head {
|
||||
uint32_t _head;
|
||||
public:
|
||||
kernel_grant_head(uint32_t head) : _head(head) {}
|
||||
virtual gntref new_ref() override;
|
||||
virtual gntref new_ref(void *addr, size_t size) override;
|
||||
virtual void free_ref(gntref& ref);
|
||||
};
|
||||
|
||||
class kernel_gntalloc : public gntalloc {
|
||||
static constexpr int _tx_grants = 256;
|
||||
static constexpr int _rx_grants = 256;
|
||||
|
||||
uint32_t tx_head, rx_head;
|
||||
protected:
|
||||
void *new_frame();
|
||||
public:
|
||||
kernel_gntalloc(unsigned otherend) : gntalloc(otherend) {}
|
||||
|
||||
virtual gntref alloc_ref() override;
|
||||
virtual grant_head *alloc_ref(unsigned refs) override;
|
||||
friend class kernel_grant_head;
|
||||
};
|
||||
|
||||
// FIXME: It is not actually that far-fetched to run seastar ontop of raw pv,
|
||||
// and in that case we'll need to have an extra step here
|
||||
inline uint64_t
|
||||
virt_to_mfn(void *virt) {
|
||||
return virt_to_phys(virt) >> 12;
|
||||
}
|
||||
|
||||
gntref kernel_grant_head::new_ref() {
|
||||
auto gnt = dynamic_cast<kernel_gntalloc *>(gntalloc::instance());
|
||||
|
||||
auto ref = gnttab_claim_grant_reference(&_head);
|
||||
auto page = gnt->new_frame();
|
||||
gnttab_grant_foreign_access_ref(ref, gnt->_otherend, virt_to_mfn(page), 0);
|
||||
return gntref(ref, page);
|
||||
}
|
||||
|
||||
gntref kernel_grant_head::new_ref(void *addr, size_t size) {
|
||||
|
||||
auto gnt = dynamic_cast<kernel_gntalloc *>(gntalloc::instance());
|
||||
|
||||
// FIXME: if we can guarantee that the packet allocation came from malloc, not
|
||||
// mmap, we can grant it directly, without copying. We would also have to propagate
|
||||
// the offset information, but that is easier
|
||||
auto page = gnt->new_frame();
|
||||
memcpy(page, addr, size);
|
||||
|
||||
auto ref = gnttab_claim_grant_reference(&_head);
|
||||
gnttab_grant_foreign_access_ref(ref, gnt->_otherend, virt_to_mfn(page), 0);
|
||||
return gntref(ref, page);
|
||||
}
|
||||
|
||||
void *kernel_gntalloc::new_frame() {
|
||||
return aligned_alloc(4096, 4096);
|
||||
}
|
||||
|
||||
gntref kernel_gntalloc::alloc_ref() {
|
||||
|
||||
void *page = new_frame();
|
||||
uint32_t ref;
|
||||
|
||||
if (gnttab_grant_foreign_access(_otherend, virt_to_mfn(page), 0, &ref)) {
|
||||
throw std::runtime_error("Failed to initialize allocate grant\n");
|
||||
}
|
||||
|
||||
return gntref(int(ref), page);
|
||||
}
|
||||
|
||||
grant_head *kernel_gntalloc::alloc_ref(unsigned nr_ents) {
|
||||
|
||||
std::vector<gntref> v;
|
||||
uint32_t head;
|
||||
|
||||
if (gnttab_alloc_grant_references(nr_ents, &head)) {
|
||||
throw std::runtime_error("Failed to initialize allocate grant\n");
|
||||
}
|
||||
|
||||
return new kernel_grant_head(head);
|
||||
}
|
||||
|
||||
void kernel_grant_head::free_ref(gntref& ref) {
|
||||
gnttab_end_foreign_access_ref(ref.xen_id);
|
||||
gnttab_release_grant_reference(&_head, ref.xen_id);
|
||||
free(ref.page);
|
||||
ref = invalid_ref;
|
||||
}
|
||||
#endif
|
||||
|
||||
gntalloc *gntalloc::_instance = nullptr;
|
||||
gntalloc *gntalloc::instance(bool userspace, unsigned otherend) {
|
||||
|
||||
if (!_instance) {
|
||||
#ifdef HAVE_OSV
|
||||
if (!userspace) {
|
||||
_instance = new kernel_gntalloc(otherend);
|
||||
} else
|
||||
#endif
|
||||
_instance = new userspace_gntalloc(otherend);
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
gntalloc *gntalloc::instance() {
|
||||
|
||||
if (!_instance) {
|
||||
throw std::runtime_error("Acquiring grant instance without specifying otherend: invalid context");
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
#ifndef _XEN_GNTALLOC_HH
|
||||
#define _XEN_GNTALLOC_HH
|
||||
|
||||
#include "core/posix.hh"
|
||||
|
||||
namespace xen {
|
||||
|
||||
class gntref {
|
||||
public:
|
||||
int xen_id;
|
||||
void* page;
|
||||
bool operator==(const gntref &a) { return (xen_id == a.xen_id) && (page == a.page); }
|
||||
gntref& operator=(const gntref &a) { xen_id = a.xen_id; page = a.page; return *this; }
|
||||
gntref(int id, void *page) : xen_id(id), page(page) {}
|
||||
gntref() : xen_id(-1), page(nullptr) {}
|
||||
operator bool() const { return xen_id != -1 && page != nullptr; }
|
||||
};
|
||||
|
||||
class gntalloc;
|
||||
|
||||
class grant_head {
|
||||
protected:
|
||||
unsigned _id = 0;
|
||||
public:
|
||||
virtual gntref new_ref() = 0;
|
||||
virtual gntref new_ref(void *addr, size_t size) = 0;
|
||||
virtual void free_ref(gntref& ref) = 0;
|
||||
};
|
||||
|
||||
class gntalloc {
|
||||
protected:
|
||||
static gntalloc *_instance;
|
||||
unsigned _otherend;
|
||||
public:
|
||||
static gntalloc *instance(bool userspace, unsigned otherend);
|
||||
static gntalloc *instance();
|
||||
gntalloc(unsigned otherend) : _otherend(otherend) {}
|
||||
virtual gntref alloc_ref() = 0;
|
||||
// The kernel interface can defer allocation, userspace allocation
|
||||
// cannot. The boolean "alloc" tell us whether or not we should allocate
|
||||
// now or try to defer.
|
||||
virtual grant_head *alloc_ref(unsigned nr_ents) = 0;
|
||||
friend class grant_head;
|
||||
};
|
||||
|
||||
extern gntref invalid_ref;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
#ifndef _XEN_HH
|
||||
#define _XEN_HH
|
||||
// Those should come directly from the OSv three. However, the xen exported
|
||||
// functions are not currently living in osv/include, but spread around the
|
||||
// BSD directory. We should move them there ASAP and then use them here.
|
||||
extern int
|
||||
bind_listening_port_to_irq(unsigned int remote_domain, int * port);
|
||||
extern int
|
||||
evtchn_from_irq(int irq);
|
||||
extern int
|
||||
notify_remote_via_evtchn(int port);
|
||||
extern void
|
||||
unmask_evtchn(int port);
|
||||
extern "C" int
|
||||
intr_add_handler(const char *name, int vector, void *filter,
|
||||
void (*handler)(void *arg), void *arg, int flags,
|
||||
void **cookiep);
|
||||
|
||||
extern int
|
||||
gnttab_alloc_grant_references(uint16_t count, uint32_t *head);
|
||||
extern int
|
||||
gnttab_claim_grant_reference(uint32_t *private_head);
|
||||
|
||||
extern int
|
||||
gnttab_grant_foreign_access(uint16_t domid, unsigned long frame, int readonly, uint32_t *result);
|
||||
extern void gnttab_grant_foreign_access_ref(uint32_t ref, uint16_t domid, unsigned long frame, int readonly);
|
||||
extern void
|
||||
gnttab_release_grant_reference(uint32_t *private_head, unsigned ref);
|
||||
extern int
|
||||
gnttab_end_foreign_access_ref(unsigned ref);
|
||||
|
||||
extern "C" uint64_t
|
||||
virt_to_phys(void *virt);
|
||||
#endif
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
#include "core/reactor.hh"
|
||||
#include "xenstore.hh"
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
|
||||
using xenstore_transaction = xenstore::xenstore_transaction;
|
||||
|
||||
xenstore_transaction xenstore::_xs_null = xenstore::xenstore_transaction();
|
||||
xenstore *xenstore::_instance = nullptr;
|
||||
|
||||
xenstore *xenstore::instance() {
|
||||
|
||||
if (!_instance) {
|
||||
_instance = new xenstore;
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
xenstore::xenstore()
|
||||
: _h(xs_daemon_open())
|
||||
{
|
||||
if (!_h) {
|
||||
throw std::runtime_error("Failed to initialize xenstore");
|
||||
}
|
||||
}
|
||||
|
||||
xenstore::~xenstore()
|
||||
{
|
||||
xs_close(_h);
|
||||
}
|
||||
|
||||
xs_transaction_t xenstore::start_transaction()
|
||||
{
|
||||
auto t = xs_transaction_start(_h);
|
||||
if (!t) {
|
||||
throw std::runtime_error("Failed to initialize xenstore transaction");
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
void xenstore::end_transaction(xs_transaction_t t)
|
||||
{
|
||||
xs_transaction_end(_h, t, false);
|
||||
}
|
||||
|
||||
void xenstore::write(std::string path, std::string value, xenstore_transaction &t)
|
||||
{
|
||||
xs_write(_h, t.t(), path.c_str(), value.c_str(), value.size());
|
||||
}
|
||||
|
||||
void xenstore::remove(std::string path, xenstore_transaction &t)
|
||||
{
|
||||
xs_rm(_h, t.t(), path.c_str());
|
||||
}
|
||||
|
||||
std::string xenstore::read(std::string path, xenstore_transaction &t)
|
||||
{
|
||||
unsigned int len;
|
||||
void *ret = xs_read(_h, t.t(), path.c_str(), &len);
|
||||
std::string str(ret ? (const char *)ret : "");
|
||||
free(ret);
|
||||
return str;
|
||||
}
|
||||
|
||||
std::list<std::string> xenstore::ls(std::string path, xenstore_transaction &t)
|
||||
{
|
||||
unsigned int num;
|
||||
char **dir = xs_directory(_h, t.t(), path.c_str(), &num);
|
||||
|
||||
std::list<std::string> names;
|
||||
for (unsigned int i = 0; i < num; ++i) {
|
||||
names.push_back(dir[i]);
|
||||
}
|
||||
free(dir);
|
||||
|
||||
return names;
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#ifndef XENSTORE_HH_
|
||||
#define XENSTORE_HH_
|
||||
|
||||
#include <list>
|
||||
|
||||
extern "C" {
|
||||
#include <xenstore.h>
|
||||
}
|
||||
|
||||
class xenstore {
|
||||
public:
|
||||
class xenstore_transaction {
|
||||
private:
|
||||
xenstore *_x;
|
||||
protected:
|
||||
xs_transaction_t _t = 0;
|
||||
public:
|
||||
explicit xenstore_transaction(xenstore *x) : _x(x)
|
||||
, _t(x->start_transaction()) {}
|
||||
explicit xenstore_transaction() {}
|
||||
|
||||
~xenstore_transaction() { if (_t) { _x->end_transaction(_t); } }
|
||||
|
||||
xs_transaction_t t() { return _t; }
|
||||
friend class xenstore;
|
||||
};
|
||||
|
||||
protected:
|
||||
xs_transaction_t start_transaction();
|
||||
void end_transaction(xs_transaction_t t);
|
||||
|
||||
static xenstore_transaction _xs_null; // having it here simplify forward decls
|
||||
|
||||
private:
|
||||
static xenstore *_instance;
|
||||
struct xs_handle *_h;
|
||||
explicit xenstore();
|
||||
~xenstore();
|
||||
|
||||
public:
|
||||
static xenstore *instance();
|
||||
|
||||
virtual void write(std::string path, std::string value, xenstore_transaction &t = _xs_null);
|
||||
virtual void remove(std::string path, xenstore_transaction &t = _xs_null);
|
||||
|
||||
virtual std::string read(std::string path, xenstore_transaction &t = _xs_null);
|
||||
|
||||
virtual std::list<std::string> ls(std::string path, xenstore_transaction &t = _xs_null);
|
||||
template <typename T>
|
||||
T read(std::string path, xenstore_transaction &t = _xs_null) { return boost::lexical_cast<T>(read(path, t)); }
|
||||
template <typename T>
|
||||
T read_or_default(std::string path, T deflt = T(), xenstore_transaction &t = _xs_null) {
|
||||
auto val = read(path, t);
|
||||
if (val.empty()) {
|
||||
return deflt;
|
||||
} else {
|
||||
return boost::lexical_cast<T>(val);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write(std::string path, T val, xenstore_transaction &t = _xs_null) { return write(path, std::to_string(val), t); }
|
||||
};
|
||||
|
||||
|
||||
#endif /* XENSTORE_HH_ */
|
||||
@@ -1,6 +0,0 @@
|
||||
FROM fedora:21
|
||||
|
||||
RUN yum install -y gcc-c++ clang libasan libubsan hwloc hwloc-devel numactl-devel \
|
||||
python3 libaio-devel ninja-build boost-devel git ragel xen-devel \
|
||||
cryptopp-devel libpciaccess-devel libxml2-devel zlib-devel \
|
||||
jsoncpp-devel
|
||||
1
dpdk
1
dpdk
Submodule dpdk deleted from 3b5e1551b3
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#include "api_docs.hh"
|
||||
#include "handlers.hh"
|
||||
#include "json/formatter.hh"
|
||||
#include "transformers.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace httpd {
|
||||
|
||||
const sstring api_registry_builder::DEFAULT_PATH = "/api-doc";
|
||||
const sstring api_registry_builder::DEFAULT_DIR = ".";
|
||||
|
||||
}
|
||||
159
http/api_docs.hh
159
http/api_docs.hh
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef API_DOCS_HH_
|
||||
#define API_DOCS_HH_
|
||||
#include "json/json_elements.hh"
|
||||
#include "json/formatter.hh"
|
||||
#include "routes.hh"
|
||||
#include "transformers.hh"
|
||||
#include <string>
|
||||
|
||||
namespace httpd {
|
||||
|
||||
struct api_doc : public json::json_base {
|
||||
json::json_element<std::string> path;
|
||||
json::json_element<std::string> description;
|
||||
|
||||
void register_params() {
|
||||
add(&path, "path");
|
||||
add(&description, "description");
|
||||
|
||||
}
|
||||
api_doc() {
|
||||
register_params();
|
||||
}
|
||||
api_doc(const api_doc & e) {
|
||||
register_params();
|
||||
path = e.path;
|
||||
description = e.description;
|
||||
}
|
||||
template<class T>
|
||||
api_doc& operator=(const T& e) {
|
||||
path = e.path;
|
||||
description = e.description;
|
||||
return *this;
|
||||
}
|
||||
api_doc& operator=(const api_doc& e) {
|
||||
path = e.path;
|
||||
description = e.description;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct api_docs : public json::json_base {
|
||||
json::json_element<std::string> apiVersion;
|
||||
json::json_element<std::string> swaggerVersion;
|
||||
json::json_list<api_doc> apis;
|
||||
|
||||
void register_params() {
|
||||
add(&apiVersion, "apiVersion");
|
||||
add(&swaggerVersion, "swaggerVersion");
|
||||
add(&apis, "apis");
|
||||
|
||||
}
|
||||
api_docs() {
|
||||
apiVersion = "0.0.1";
|
||||
swaggerVersion = "1.2";
|
||||
register_params();
|
||||
}
|
||||
api_docs(const api_docs & e) {
|
||||
apiVersion = "0.0.1";
|
||||
swaggerVersion = "1.2";
|
||||
register_params();
|
||||
}
|
||||
template<class T>
|
||||
api_docs& operator=(const T& e) {
|
||||
apis = e.apis;
|
||||
return *this;
|
||||
}
|
||||
api_docs& operator=(const api_docs& e) {
|
||||
apis = e.apis;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class api_registry : public handler_base {
|
||||
sstring _base_path;
|
||||
sstring _file_directory;
|
||||
api_docs _docs;
|
||||
routes& _routes;
|
||||
|
||||
public:
|
||||
api_registry(routes& routes, const sstring& file_directory,
|
||||
const sstring& base_path)
|
||||
: _base_path(base_path), _file_directory(file_directory), _routes(
|
||||
routes) {
|
||||
_routes.put(GET, _base_path, this);
|
||||
}
|
||||
future<std::unique_ptr<reply>> handle(const sstring& path,
|
||||
std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
|
||||
rep->_content = json::formatter::to_json(_docs);
|
||||
rep->done("json");
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
}
|
||||
|
||||
void reg(const sstring& api, const sstring& description,
|
||||
const sstring& alternative_path = "") {
|
||||
api_doc doc;
|
||||
doc.description = description;
|
||||
doc.path = "/" + api;
|
||||
_docs.apis.push(doc);
|
||||
sstring path =
|
||||
(alternative_path == "") ?
|
||||
_file_directory + api + ".json" : alternative_path;
|
||||
file_handler* index = new file_handler(path,
|
||||
new content_replace("json"));
|
||||
_routes.put(GET, _base_path + "/" + api, index);
|
||||
}
|
||||
};
|
||||
|
||||
class api_registry_builder {
|
||||
sstring _file_directory;
|
||||
sstring _base_path;
|
||||
|
||||
public:
|
||||
static const sstring DEFAULT_DIR;
|
||||
static const sstring DEFAULT_PATH;
|
||||
|
||||
api_registry_builder(const sstring& file_directory = DEFAULT_DIR,
|
||||
const sstring& base_path = DEFAULT_PATH)
|
||||
: _file_directory(file_directory), _base_path(base_path) {
|
||||
}
|
||||
|
||||
void set_api_doc(routes& r) {
|
||||
new api_registry(r, _file_directory, _base_path);
|
||||
}
|
||||
|
||||
void register_function(routes& r, const sstring& api,
|
||||
const sstring& description, const sstring& alternative_path = "") {
|
||||
auto h = r.get_exact_match(GET, _base_path);
|
||||
if (h) {
|
||||
// if a handler is found, it was added there by the api_registry_builder
|
||||
// with the set_api_doc method, so we know it's the type
|
||||
static_cast<api_registry*>(h)->reg(api, description, alternative_path);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* API_DOCS_HH_ */
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#include "common.hh"
|
||||
|
||||
namespace httpd {
|
||||
|
||||
operation_type str2type(const sstring& type) {
|
||||
if (type == "DELETE") {
|
||||
return DELETE;
|
||||
}
|
||||
if (type == "POST") {
|
||||
return POST;
|
||||
}
|
||||
if (type == "PUT") {
|
||||
return PUT;
|
||||
}
|
||||
return GET;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef COMMON_HH_
|
||||
#define COMMON_HH_
|
||||
|
||||
#include <unordered_map>
|
||||
#include "core/sstring.hh"
|
||||
|
||||
namespace httpd {
|
||||
|
||||
|
||||
class parameters {
|
||||
std::unordered_map<sstring, sstring> params;
|
||||
public:
|
||||
const sstring& path(const sstring& key) const {
|
||||
return params.at(key);
|
||||
}
|
||||
|
||||
sstring operator[](const sstring& key) const {
|
||||
return params.at(key).substr(1);
|
||||
}
|
||||
|
||||
const sstring& at(const sstring& key) const {
|
||||
return path(key);
|
||||
}
|
||||
|
||||
bool exists(const sstring& key) const {
|
||||
return params.find(key) != params.end();
|
||||
}
|
||||
|
||||
void set(const sstring& key, const sstring& value) {
|
||||
params[key] = value;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
params.clear();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
enum operation_type {
|
||||
GET, POST, PUT, DELETE, NUM_OPERATION
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate the string command to operation type
|
||||
* @param type the string "GET" or "POST"
|
||||
* @return the operation_type
|
||||
*/
|
||||
operation_type str2type(const sstring& type);
|
||||
|
||||
}
|
||||
|
||||
#endif /* COMMON_HH_ */
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef EXCEPTION_HH_
|
||||
#define EXCEPTION_HH_
|
||||
|
||||
#include "reply.hh"
|
||||
#include "json/json_elements.hh"
|
||||
|
||||
namespace httpd {
|
||||
|
||||
/**
|
||||
* The base_exception is a base for all http exception.
|
||||
* It contains a message that will be return as the message content
|
||||
* and a status that will be return as a status code.
|
||||
*/
|
||||
class base_exception : public std::exception {
|
||||
public:
|
||||
base_exception(const std::string& msg, reply::status_type status)
|
||||
: _msg(msg), _status(status) {
|
||||
}
|
||||
|
||||
virtual const char* what() const throw () {
|
||||
return _msg.c_str();
|
||||
}
|
||||
|
||||
reply::status_type status() const {
|
||||
return _status;
|
||||
}
|
||||
|
||||
virtual const std::string& str() const {
|
||||
return _msg;
|
||||
}
|
||||
private:
|
||||
std::string _msg;
|
||||
reply::status_type _status;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Throwing this exception will result in a redirect to the given url
|
||||
*/
|
||||
class redirect_exception : public base_exception {
|
||||
public:
|
||||
redirect_exception(const std::string& url)
|
||||
: base_exception("", reply::status_type::moved_permanently), url(
|
||||
url) {
|
||||
}
|
||||
std::string url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Throwing this exception will result in a 404 not found result
|
||||
*/
|
||||
class not_found_exception : public base_exception {
|
||||
public:
|
||||
not_found_exception(const std::string& msg = "Not found")
|
||||
: base_exception(msg, reply::status_type::not_found) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Throwing this exception will result in a 400 bad request result
|
||||
*/
|
||||
|
||||
class bad_request_exception : public base_exception {
|
||||
public:
|
||||
bad_request_exception(const std::string& msg)
|
||||
: base_exception(msg, reply::status_type::bad_request) {
|
||||
}
|
||||
};
|
||||
|
||||
class bad_param_exception : public bad_request_exception {
|
||||
public:
|
||||
bad_param_exception(const std::string& msg)
|
||||
: bad_request_exception(msg) {
|
||||
}
|
||||
};
|
||||
|
||||
class missing_param_exception : public bad_request_exception {
|
||||
public:
|
||||
missing_param_exception(const std::string& param)
|
||||
: bad_request_exception(
|
||||
std::string("Missing mandatory parameter '") + param + "'") {
|
||||
}
|
||||
};
|
||||
|
||||
class server_error_exception : public base_exception {
|
||||
public:
|
||||
server_error_exception(const std::string& msg)
|
||||
: base_exception(msg, reply::status_type::internal_server_error) {
|
||||
}
|
||||
};
|
||||
|
||||
class json_exception : public json::json_base {
|
||||
public:
|
||||
json::json_element<std::string> _msg;
|
||||
json::json_element<int> _code;
|
||||
void register_params() {
|
||||
add(&_msg, "message");
|
||||
add(&_code, "code");
|
||||
}
|
||||
|
||||
json_exception(const base_exception & e) {
|
||||
set(e.str(), e.status());
|
||||
}
|
||||
|
||||
json_exception(const std::exception& e) {
|
||||
set(e.what(), reply::status_type::internal_server_error);
|
||||
}
|
||||
private:
|
||||
void set(const std::string& msg, reply::status_type code) {
|
||||
register_params();
|
||||
_msg = msg;
|
||||
_code = (int) code;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* EXCEPTION_HH_ */
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#include "file_handler.hh"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include "core/reactor.hh"
|
||||
#include "core/fstream.hh"
|
||||
#include "core/shared_ptr.hh"
|
||||
#include "core/app-template.hh"
|
||||
#include "exception.hh"
|
||||
|
||||
namespace httpd {
|
||||
|
||||
directory_handler::directory_handler(const sstring& doc_root,
|
||||
file_transformer* transformer)
|
||||
: file_interaction_handler(transformer), doc_root(doc_root) {
|
||||
}
|
||||
|
||||
future<std::unique_ptr<reply>> directory_handler::handle(const sstring& path,
|
||||
std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
|
||||
sstring full_path = doc_root + req->param["path"];
|
||||
auto h = this;
|
||||
return engine().file_type(full_path).then(
|
||||
[h, full_path, req = std::move(req), rep = std::move(rep)](auto val) mutable {
|
||||
if (val) {
|
||||
if (val.value() == directory_entry_type::directory) {
|
||||
if (h->redirect_if_needed(*req.get(), *rep.get())) {
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
}
|
||||
full_path += "/index.html";
|
||||
}
|
||||
return h->read(full_path, std::move(req), std::move(rep));
|
||||
}
|
||||
rep->set_status(reply::status_type::not_found).done();
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
file_interaction_handler::~file_interaction_handler() {
|
||||
delete transformer;
|
||||
}
|
||||
|
||||
sstring file_interaction_handler::get_extension(const sstring& file) {
|
||||
size_t last_slash_pos = file.find_last_of('/');
|
||||
size_t last_dot_pos = file.find_last_of('.');
|
||||
sstring extension;
|
||||
if (last_dot_pos != sstring::npos && last_dot_pos > last_slash_pos) {
|
||||
extension = file.substr(last_dot_pos + 1);
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
struct reader {
|
||||
reader(file f, std::unique_ptr<reply> rep)
|
||||
: is(
|
||||
make_file_input_stream(make_lw_shared<file>(std::move(f)),
|
||||
0, 4096)), _rep(std::move(rep)) {
|
||||
}
|
||||
input_stream<char> is;
|
||||
std::unique_ptr<reply> _rep;
|
||||
|
||||
// for input_stream::consume():
|
||||
using unconsumed_remainder = std::experimental::optional<temporary_buffer<char>>;
|
||||
future<unconsumed_remainder> operator()(temporary_buffer<char> data) {
|
||||
if (data.empty()) {
|
||||
_rep->done();
|
||||
return make_ready_future<unconsumed_remainder>(std::move(data));
|
||||
} else {
|
||||
_rep->_content.append(data.get(), data.size());
|
||||
return make_ready_future<unconsumed_remainder>();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
future<std::unique_ptr<reply>> file_interaction_handler::read(
|
||||
const sstring& file_name, std::unique_ptr<request> req,
|
||||
std::unique_ptr<reply> rep) {
|
||||
sstring extension = get_extension(file_name);
|
||||
rep->set_content_type(extension);
|
||||
return engine().open_file_dma(file_name, open_flags::ro).then(
|
||||
[rep = std::move(rep), extension, this, req = std::move(req)](file f) mutable {
|
||||
std::shared_ptr<reader> r = std::make_shared<reader>(std::move(f), std::move(rep));
|
||||
|
||||
return r->is.consume(*r).then([r, extension, this, req = std::move(req)]() {
|
||||
if (transformer != nullptr) {
|
||||
transformer->transform(r->_rep->_content, *req, extension);
|
||||
}
|
||||
r->_rep->done();
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(r->_rep));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool file_interaction_handler::redirect_if_needed(const request& req,
|
||||
reply& rep) const {
|
||||
if (req._url.length() == 0 || req._url.back() != '/') {
|
||||
rep.set_status(reply::status_type::moved_permanently);
|
||||
rep._headers["Location"] = req.get_url() + "/";
|
||||
rep.done();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
future<std::unique_ptr<reply>> file_handler::handle(const sstring& path,
|
||||
std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
|
||||
if (force_path && redirect_if_needed(*req.get(), *rep.get())) {
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
}
|
||||
return read(file, std::move(req), std::move(rep));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef HTTP_FILE_HANDLER_HH_
|
||||
#define HTTP_FILE_HANDLER_HH_
|
||||
|
||||
#include "handlers.hh"
|
||||
|
||||
namespace httpd {
|
||||
/**
|
||||
* This is a base class for file transformer.
|
||||
*
|
||||
* File transformer adds the ability to modify a file content before returning
|
||||
* the results.
|
||||
*
|
||||
* The transformer decides according to the file extension if transforming is
|
||||
* needed.
|
||||
*/
|
||||
class file_transformer {
|
||||
public:
|
||||
/**
|
||||
* Any file transformer should implement this method.
|
||||
* @param content the content to transform
|
||||
* @param req the request
|
||||
* @param extension the file extension originating the content
|
||||
*/
|
||||
virtual void transform(sstring& content, const request& req,
|
||||
const sstring& extension) = 0;
|
||||
|
||||
virtual ~file_transformer() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* A base class for handlers that interact with files.
|
||||
* directory and file handlers both share some common logic
|
||||
* with regards to file handling.
|
||||
* they both needs to read a file from the disk, optionally transform it,
|
||||
* and return the result or page not found on error
|
||||
*/
|
||||
class file_interaction_handler : public handler_base {
|
||||
public:
|
||||
file_interaction_handler(file_transformer* p = nullptr)
|
||||
: transformer(p) {
|
||||
|
||||
}
|
||||
|
||||
~file_interaction_handler();
|
||||
|
||||
/**
|
||||
* Allows setting a transformer to be used with the files returned.
|
||||
* @param t the file transformer to use
|
||||
* @return this
|
||||
*/
|
||||
file_interaction_handler* set_transformer(file_transformer* t) {
|
||||
transformer = t;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* if the url ends without a slash redirect
|
||||
* @param req the request
|
||||
* @param rep the reply
|
||||
* @return true on redirect
|
||||
*/
|
||||
bool redirect_if_needed(const request& req, reply& rep) const;
|
||||
|
||||
/**
|
||||
* A helper method that returns the file extension.
|
||||
* @param file the file to check
|
||||
* @return the file extension
|
||||
*/
|
||||
static sstring get_extension(const sstring& file);
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* read a file from the disk and return it in the replay.
|
||||
* @param file the full path to a file on the disk
|
||||
* @param req the reuest
|
||||
* @param rep the reply
|
||||
*/
|
||||
future<std::unique_ptr<reply> > read(const sstring& file,
|
||||
std::unique_ptr<request> req, std::unique_ptr<reply> rep);
|
||||
file_transformer* transformer;
|
||||
};
|
||||
|
||||
/**
|
||||
* The directory handler get a disk path in the
|
||||
* constructor.
|
||||
* and expect a path parameter in the handle method.
|
||||
* it would concatenate the two and return the file
|
||||
* e.g. if the path is /usr/mgmt/public in the path
|
||||
* parameter is index.html
|
||||
* handle will return the content of /usr/mgmt/public/index.html
|
||||
*/
|
||||
class directory_handler : public file_interaction_handler {
|
||||
public:
|
||||
|
||||
/**
|
||||
* The directory handler map a base path and a path parameter to a file
|
||||
* @param doc_root the root directory to search the file from.
|
||||
* For example if the root is '/usr/mgmt/public' and the path parameter
|
||||
* will be '/css/style.css' the file wil be /usr/mgmt/public/css/style.css'
|
||||
*/
|
||||
explicit directory_handler(const sstring& doc_root,
|
||||
file_transformer* transformer = nullptr);
|
||||
|
||||
future<std::unique_ptr<reply>> handle(const sstring& path,
|
||||
std::unique_ptr<request> req, std::unique_ptr<reply> rep) override;
|
||||
|
||||
private:
|
||||
sstring doc_root;
|
||||
};
|
||||
|
||||
/**
|
||||
* The file handler get a path to a file on the disk
|
||||
* in the constructor.
|
||||
* it will always return the content of the file.
|
||||
*/
|
||||
class file_handler : public file_interaction_handler {
|
||||
public:
|
||||
|
||||
/**
|
||||
* The file handler map a file to a url
|
||||
* @param file the full path to the file on the disk
|
||||
*/
|
||||
explicit file_handler(const sstring& file, file_transformer* transformer =
|
||||
nullptr, bool force_path = true)
|
||||
: file_interaction_handler(transformer), file(file), force_path(
|
||||
force_path) {
|
||||
}
|
||||
|
||||
future<std::unique_ptr<reply>> handle(const sstring& path,
|
||||
std::unique_ptr<request> req, std::unique_ptr<reply> rep) override;
|
||||
|
||||
private:
|
||||
sstring file;
|
||||
bool force_path;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* HTTP_FILE_HANDLER_HH_ */
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "handlers.hh"
|
||||
#include <functional>
|
||||
#include "json/json_elements.hh"
|
||||
|
||||
namespace httpd {
|
||||
|
||||
/**
|
||||
* A request function is a lambda expression that gets only the request
|
||||
* as its parameter
|
||||
*/
|
||||
typedef std::function<sstring(const_req req)> request_function;
|
||||
|
||||
/**
|
||||
* A handle function is a lambda expression that gets request and reply
|
||||
*/
|
||||
typedef std::function<sstring(const_req req, reply&)> handle_function;
|
||||
|
||||
/**
|
||||
* A json request function is a lambda expression that gets only the request
|
||||
* as its parameter and return a json response.
|
||||
* Using the json response is done implicitly.
|
||||
*/
|
||||
typedef std::function<json::json_return_type(const_req req)> json_request_function;
|
||||
|
||||
/**
|
||||
* A future_json_function is a function that returns a future json reponse.
|
||||
* Similiar to the json_request_function, using the json reponse is done
|
||||
* implicitly.
|
||||
*/
|
||||
typedef std::function<
|
||||
future<json::json_return_type>(std::unique_ptr<request> req)> future_json_function;
|
||||
/**
|
||||
* The function handler get a lambda expression in the constructor.
|
||||
* it will call that expression to get the result
|
||||
* This is suited for very simple handlers
|
||||
*
|
||||
*/
|
||||
class function_handler : public handler_base {
|
||||
public:
|
||||
|
||||
function_handler(const handle_function & f_handle, const sstring& type)
|
||||
: _f_handle(
|
||||
[f_handle](std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
|
||||
rep->_content += f_handle(*req.get(), *rep.get());
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
}), _type(type) {
|
||||
}
|
||||
|
||||
function_handler(const request_function & _handle, const sstring& type)
|
||||
: _f_handle(
|
||||
[_handle](std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
|
||||
rep->_content += _handle(*req.get());
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
}), _type(type) {
|
||||
}
|
||||
|
||||
function_handler(const json_request_function& _handle)
|
||||
: _f_handle(
|
||||
[_handle](std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
|
||||
json::json_return_type res = _handle(*req.get());
|
||||
rep->_content += res._res;
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
}), _type("json") {
|
||||
}
|
||||
|
||||
function_handler(const future_json_function& _handle)
|
||||
: _f_handle(
|
||||
[_handle](std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
|
||||
return _handle(std::move(req)).then([rep = std::move(rep)](json::json_return_type res) mutable {
|
||||
rep->_content += res._res;
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
}
|
||||
);
|
||||
}), _type("json") {
|
||||
}
|
||||
|
||||
future<std::unique_ptr<reply>> handle(const sstring& path,
|
||||
std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
|
||||
return _f_handle(std::move(req), std::move(rep)).then(
|
||||
[this](std::unique_ptr<reply> rep) {
|
||||
rep->done(_type);
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
std::function<
|
||||
future<std::unique_ptr<reply>>(std::unique_ptr<request> req,
|
||||
std::unique_ptr<reply> rep)> _f_handle;
|
||||
sstring _type;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef HANDLERS_HH_
|
||||
#define HANDLERS_HH_
|
||||
|
||||
#include "request.hh"
|
||||
#include "common.hh"
|
||||
#include "reply.hh"
|
||||
#include "core/future-util.hh"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace httpd {
|
||||
|
||||
typedef const httpd::request& const_req;
|
||||
|
||||
/**
|
||||
* handlers holds the logic for serving an incoming request.
|
||||
* All handlers inherit from the base httpserver_handler and
|
||||
* implement the handle method.
|
||||
*
|
||||
*/
|
||||
class handler_base {
|
||||
public:
|
||||
/**
|
||||
* All handlers should implement this method.
|
||||
* It fill the reply according to the request.
|
||||
* @param path the url path used in this call
|
||||
* @param params optional parameter object
|
||||
* @param req the original request
|
||||
* @param rep the reply
|
||||
*/
|
||||
virtual future<std::unique_ptr<reply> > handle(const sstring& path,
|
||||
std::unique_ptr<request> req, std::unique_ptr<reply> rep) = 0;
|
||||
|
||||
virtual ~handler_base() = default;
|
||||
|
||||
/**
|
||||
* Add a mandatory parameter
|
||||
* @param param a parameter name
|
||||
* @return a reference to the handler
|
||||
*/
|
||||
handler_base& mandatory(const sstring& param) {
|
||||
_mandatory_param.push_back(param);
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<sstring> _mandatory_param;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* HANDLERS_HH_ */
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#include "core/reactor.hh"
|
||||
#include "core/sstring.hh"
|
||||
#include "core/app-template.hh"
|
||||
#include "core/circular_buffer.hh"
|
||||
#include "core/distributed.hh"
|
||||
#include "core/queue.hh"
|
||||
#include "core/future-util.hh"
|
||||
#include "core/scollectd.hh"
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
#include <bitset>
|
||||
#include <limits>
|
||||
#include <cctype>
|
||||
#include <vector>
|
||||
#include "httpd.hh"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace httpd {
|
||||
http_stats::http_stats(http_server& server)
|
||||
: _regs{
|
||||
scollectd::add_polled_metric(
|
||||
scollectd::type_instance_id("httpd", scollectd::per_cpu_plugin_instance,
|
||||
"connections", "http-connections"),
|
||||
scollectd::make_typed(scollectd::data_type::DERIVE,
|
||||
[&server] { return server.total_connections(); })),
|
||||
scollectd::add_polled_metric(
|
||||
scollectd::type_instance_id("httpd", scollectd::per_cpu_plugin_instance,
|
||||
"current_connections", "current"),
|
||||
scollectd::make_typed(scollectd::data_type::GAUGE,
|
||||
[&server] { return server.current_connections(); })),
|
||||
scollectd::add_polled_metric(
|
||||
scollectd::type_instance_id("httpd", scollectd::per_cpu_plugin_instance,
|
||||
"http_requests", "served"),
|
||||
scollectd::make_typed(scollectd::data_type::DERIVE,
|
||||
[&server] { return server.requests_served(); })),
|
||||
} {
|
||||
}
|
||||
}
|
||||
431
http/httpd.hh
431
http/httpd.hh
@@ -1,431 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef APPS_HTTPD_HTTPD_HH_
|
||||
#define APPS_HTTPD_HTTPD_HH_
|
||||
|
||||
#include "http/request_parser.hh"
|
||||
#include "http/request.hh"
|
||||
#include "core/reactor.hh"
|
||||
#include "core/sstring.hh"
|
||||
#include <experimental/string_view>
|
||||
#include "core/app-template.hh"
|
||||
#include "core/circular_buffer.hh"
|
||||
#include "core/distributed.hh"
|
||||
#include "core/queue.hh"
|
||||
#include "core/future-util.hh"
|
||||
#include "core/scollectd.hh"
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
#include <bitset>
|
||||
#include <limits>
|
||||
#include <cctype>
|
||||
#include <vector>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include "reply.hh"
|
||||
#include "http/routes.hh"
|
||||
|
||||
namespace httpd {
|
||||
|
||||
class http_server;
|
||||
class http_stats;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class http_stats {
|
||||
scollectd::registrations _regs;
|
||||
public:
|
||||
http_stats(http_server& server);
|
||||
};
|
||||
|
||||
class http_server {
|
||||
std::vector<server_socket> _listeners;
|
||||
http_stats _stats { *this };
|
||||
uint64_t _total_connections = 0;
|
||||
uint64_t _current_connections = 0;
|
||||
uint64_t _requests_served = 0;
|
||||
uint64_t _connections_being_accepted = 0;
|
||||
sstring _date = http_date();
|
||||
timer<> _date_format_timer { [this] {_date = http_date();} };
|
||||
bool _stopping = false;
|
||||
promise<> _all_connections_stopped;
|
||||
future<> _stopped = _all_connections_stopped.get_future();
|
||||
private:
|
||||
void maybe_idle() {
|
||||
if (_stopping && !_connections_being_accepted && !_current_connections) {
|
||||
_all_connections_stopped.set_value();
|
||||
}
|
||||
}
|
||||
public:
|
||||
routes _routes;
|
||||
|
||||
http_server() {
|
||||
_date_format_timer.arm_periodic(1s);
|
||||
}
|
||||
future<> listen(ipv4_addr addr) {
|
||||
listen_options lo;
|
||||
lo.reuse_address = true;
|
||||
_listeners.push_back(engine().listen(make_ipv4_address(addr), lo));
|
||||
_stopped = when_all(std::move(_stopped), do_accepts(_listeners.size() - 1)).discard_result();
|
||||
return make_ready_future<>();
|
||||
}
|
||||
future<> stop() {
|
||||
_stopping = true;
|
||||
for (auto&& l : _listeners) {
|
||||
l.abort_accept();
|
||||
}
|
||||
for (auto&& c : _connections) {
|
||||
c.shutdown();
|
||||
}
|
||||
return std::move(_stopped);
|
||||
}
|
||||
future<> do_accepts(int which) {
|
||||
++_connections_being_accepted;
|
||||
return _listeners[which].accept().then_wrapped(
|
||||
[this, which] (future<connected_socket, socket_address> f_cs_sa) mutable {
|
||||
--_connections_being_accepted;
|
||||
if (_stopping) {
|
||||
maybe_idle();
|
||||
return;
|
||||
}
|
||||
auto cs_sa = f_cs_sa.get();
|
||||
auto conn = new connection(*this, std::get<0>(std::move(cs_sa)), std::get<1>(std::move(cs_sa)));
|
||||
conn->process().then_wrapped([this, conn] (auto&& f) {
|
||||
delete conn;
|
||||
try {
|
||||
f.get();
|
||||
} catch (std::exception& ex) {
|
||||
std::cerr << "request error " << ex.what() << std::endl;
|
||||
}
|
||||
});
|
||||
do_accepts(which);
|
||||
}).then_wrapped([] (auto f) {
|
||||
try {
|
||||
f.get();
|
||||
} catch (std::exception& ex) {
|
||||
std::cerr << "accept failed: " << ex.what() << std::endl;
|
||||
}
|
||||
});
|
||||
}
|
||||
class connection : public boost::intrusive::list_base_hook<> {
|
||||
http_server& _server;
|
||||
connected_socket _fd;
|
||||
input_stream<char> _read_buf;
|
||||
output_stream<char> _write_buf;
|
||||
static constexpr size_t limit = 4096;
|
||||
using tmp_buf = temporary_buffer<char>;
|
||||
http_request_parser _parser;
|
||||
std::unique_ptr<request> _req;
|
||||
std::unique_ptr<reply> _resp;
|
||||
// null element marks eof
|
||||
queue<std::unique_ptr<reply>> _replies { 10 };bool _done = false;
|
||||
public:
|
||||
connection(http_server& server, connected_socket&& fd,
|
||||
socket_address addr)
|
||||
: _server(server), _fd(std::move(fd)), _read_buf(_fd.input()), _write_buf(
|
||||
_fd.output()) {
|
||||
++_server._total_connections;
|
||||
++_server._current_connections;
|
||||
_server._connections.push_back(*this);
|
||||
}
|
||||
~connection() {
|
||||
--_server._current_connections;
|
||||
_server._connections.erase(_server._connections.iterator_to(*this));
|
||||
_server.maybe_idle();
|
||||
}
|
||||
future<> process() {
|
||||
// Launch read and write "threads" simultaneously:
|
||||
return when_all(read(), respond()).then(
|
||||
[] (std::tuple<future<>, future<>> joined) {
|
||||
// FIXME: notify any exceptions in joined?
|
||||
return make_ready_future<>();
|
||||
});
|
||||
}
|
||||
void shutdown() {
|
||||
_fd.shutdown_input();
|
||||
_fd.shutdown_output();
|
||||
}
|
||||
future<> read() {
|
||||
return do_until([this] {return _done;}, [this] {
|
||||
return read_one();
|
||||
}).then_wrapped([this] (future<> f) {
|
||||
// swallow error
|
||||
// FIXME: count it?
|
||||
return _replies.push_eventually( {});
|
||||
});
|
||||
}
|
||||
future<> read_one() {
|
||||
_parser.init();
|
||||
return _read_buf.consume(_parser).then([this] () mutable {
|
||||
if (_parser.eof()) {
|
||||
_done = true;
|
||||
return make_ready_future<>();
|
||||
}
|
||||
++_server._requests_served;
|
||||
std::unique_ptr<httpd::request> req = _parser.get_parsed_request();
|
||||
|
||||
return _replies.not_full().then([req = std::move(req), this] () mutable {
|
||||
return generate_reply(std::move(req));
|
||||
}).then([this](bool done) {
|
||||
_done = done;
|
||||
});
|
||||
});
|
||||
}
|
||||
future<> respond() {
|
||||
return _replies.pop_eventually().then(
|
||||
[this] (std::unique_ptr<reply> resp) {
|
||||
if (!resp) {
|
||||
// eof
|
||||
return make_ready_future<>();
|
||||
}
|
||||
_resp = std::move(resp);
|
||||
return start_response().then([this] {
|
||||
return respond();
|
||||
});
|
||||
});
|
||||
}
|
||||
future<> start_response() {
|
||||
_resp->_headers["Server"] = "Seastar httpd";
|
||||
_resp->_headers["Date"] = _server._date;
|
||||
_resp->_headers["Content-Length"] = to_sstring(
|
||||
_resp->_content.size());
|
||||
return _write_buf.write(_resp->_response_line.begin(),
|
||||
_resp->_response_line.size()).then([this] {
|
||||
return write_reply_headers(_resp->_headers.begin());
|
||||
}).then([this] {
|
||||
return _write_buf.write("\r\n", 2);
|
||||
}).then([this] {
|
||||
return write_body();
|
||||
}).then([this] {
|
||||
return _write_buf.flush();
|
||||
}).then([this] {
|
||||
_resp.reset();
|
||||
});
|
||||
}
|
||||
future<> write_reply_headers(
|
||||
std::unordered_map<sstring, sstring>::iterator hi) {
|
||||
if (hi == _resp->_headers.end()) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
return _write_buf.write(hi->first.begin(), hi->first.size()).then(
|
||||
[this] {
|
||||
return _write_buf.write(": ", 2);
|
||||
}).then([hi, this] {
|
||||
return _write_buf.write(hi->second.begin(), hi->second.size());
|
||||
}).then([this] {
|
||||
return _write_buf.write("\r\n", 2);
|
||||
}).then([hi, this] () mutable {
|
||||
return write_reply_headers(++hi);
|
||||
});
|
||||
}
|
||||
|
||||
static short hex_to_byte(char c) {
|
||||
if (c >='a' && c <= 'z') {
|
||||
return c - 'a' + 10;
|
||||
} else if (c >='A' && c <= 'Z') {
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
return c - '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a hex encoded 2 bytes substring to char
|
||||
*/
|
||||
static char hexstr_to_char(const std::experimental::string_view& in, size_t from) {
|
||||
|
||||
return static_cast<char>(hex_to_byte(in[from]) * 16 + hex_to_byte(in[from + 1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* URL_decode a substring and place it in the given out sstring
|
||||
*/
|
||||
static bool url_decode(const std::experimental::string_view& in, sstring& out) {
|
||||
size_t pos = 0;
|
||||
char buff[in.length()];
|
||||
for (size_t i = 0; i < in.length(); ++i) {
|
||||
if (in[i] == '%') {
|
||||
if (i + 3 <= in.size()) {
|
||||
buff[pos++] = hexstr_to_char(in, i + 1);
|
||||
i += 2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (in[i] == '+') {
|
||||
buff[pos++] = ' ';
|
||||
} else {
|
||||
buff[pos++] = in[i];
|
||||
}
|
||||
}
|
||||
out = sstring(buff, pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single query parameter to the parameter list
|
||||
*/
|
||||
static void add_param(request& req, const std::experimental::string_view& param) {
|
||||
size_t split = param.find('=');
|
||||
|
||||
if (split >= param.length() - 1) {
|
||||
sstring key;
|
||||
if (url_decode(param.substr(0,split) , key)) {
|
||||
req.query_parameters[key] = "";
|
||||
}
|
||||
} else {
|
||||
sstring key;
|
||||
sstring value;
|
||||
if (url_decode(param.substr(0,split), key)
|
||||
&& url_decode(param.substr(split + 1), value)) {
|
||||
req.query_parameters[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query parameters in the request objects.
|
||||
* query param appear after the question mark and are separated
|
||||
* by the ampersand sign
|
||||
*/
|
||||
static sstring set_query_param(request& req) {
|
||||
size_t pos = req._url.find('?');
|
||||
if (pos == sstring::npos) {
|
||||
return req._url;
|
||||
}
|
||||
size_t curr = pos + 1;
|
||||
size_t end_param;
|
||||
std::experimental::string_view url = req._url;
|
||||
while ((end_param = req._url.find('&', curr)) != sstring::npos) {
|
||||
add_param(req, url.substr(curr, end_param - curr) );
|
||||
curr = end_param + 1;
|
||||
}
|
||||
add_param(req, url.substr(curr));
|
||||
return req._url.substr(0, pos);
|
||||
}
|
||||
|
||||
future<bool> generate_reply(std::unique_ptr<request> req) {
|
||||
auto resp = std::make_unique<reply>();
|
||||
bool conn_keep_alive = false;
|
||||
bool conn_close = false;
|
||||
auto it = req->_headers.find("Connection");
|
||||
if (it != req->_headers.end()) {
|
||||
if (it->second == "Keep-Alive") {
|
||||
conn_keep_alive = true;
|
||||
} else if (it->second == "Close") {
|
||||
conn_close = true;
|
||||
}
|
||||
}
|
||||
bool should_close;
|
||||
// TODO: Handle HTTP/2.0 when it releases
|
||||
resp->set_version(req->_version);
|
||||
|
||||
if (req->_version == "1.0") {
|
||||
if (conn_keep_alive) {
|
||||
resp->_headers["Connection"] = "Keep-Alive";
|
||||
}
|
||||
should_close = !conn_keep_alive;
|
||||
} else if (req->_version == "1.1") {
|
||||
should_close = conn_close;
|
||||
} else {
|
||||
// HTTP/0.9 goes here
|
||||
should_close = true;
|
||||
}
|
||||
sstring url = set_query_param(*req.get());
|
||||
return _server._routes.handle(url, std::move(req), std::move(resp)).
|
||||
// Caller guarantees enough room
|
||||
then([this, should_close](std::unique_ptr<reply> rep) {
|
||||
this->_replies.push(std::move(rep));
|
||||
return make_ready_future<bool>(should_close);
|
||||
});
|
||||
}
|
||||
future<> write_body() {
|
||||
return _write_buf.write(_resp->_content.begin(),
|
||||
_resp->_content.size());
|
||||
}
|
||||
};
|
||||
uint64_t total_connections() const {
|
||||
return _total_connections;
|
||||
}
|
||||
uint64_t current_connections() const {
|
||||
return _current_connections;
|
||||
}
|
||||
uint64_t requests_served() const {
|
||||
return _requests_served;
|
||||
}
|
||||
static sstring http_date() {
|
||||
auto t = ::time(nullptr);
|
||||
struct tm tm;
|
||||
gmtime_r(&t, &tm);
|
||||
char tmp[100];
|
||||
strftime(tmp, sizeof(tmp), "%d %b %Y %H:%M:%S GMT", &tm);
|
||||
return tmp;
|
||||
}
|
||||
private:
|
||||
boost::intrusive::list<connection> _connections;
|
||||
};
|
||||
|
||||
/*
|
||||
* A helper class to start, set and listen an http server
|
||||
* typical use would be:
|
||||
*
|
||||
* auto server = new http_server_control();
|
||||
* server->start().then([server] {
|
||||
* server->set_routes(set_routes);
|
||||
* }).then([server, port] {
|
||||
* server->listen(port);
|
||||
* }).then([port] {
|
||||
* std::cout << "Seastar HTTP server listening on port " << port << " ...\n";
|
||||
* });
|
||||
*/
|
||||
class http_server_control {
|
||||
distributed<http_server>* _server_dist;
|
||||
public:
|
||||
http_server_control() : _server_dist(new distributed<http_server>) {
|
||||
}
|
||||
|
||||
future<> start() {
|
||||
return _server_dist->start();
|
||||
}
|
||||
|
||||
future<> stop() {
|
||||
return _server_dist->stop();
|
||||
}
|
||||
|
||||
future<> set_routes(std::function<void(routes& r)> fun) {
|
||||
return _server_dist->invoke_on_all([fun](http_server& server) {
|
||||
fun(server._routes);
|
||||
});
|
||||
}
|
||||
|
||||
future<> listen(uint16_t port) {
|
||||
return _server_dist->invoke_on_all(&http_server::listen, ipv4_addr {port});
|
||||
}
|
||||
|
||||
distributed<http_server>& server() {
|
||||
return *_server_dist;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* APPS_HTTPD_HTTPD_HH_ */
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#include "json_path.hh"
|
||||
|
||||
namespace httpd {
|
||||
|
||||
using namespace std;
|
||||
|
||||
void path_description::set(routes& _routes, handler_base* handler) const {
|
||||
for (auto& i : mandatory_queryparams) {
|
||||
handler->mandatory(i);
|
||||
}
|
||||
|
||||
if (params.size() == 0)
|
||||
_routes.put(operations.method, path, handler);
|
||||
else {
|
||||
match_rule* rule = new match_rule(handler);
|
||||
rule->add_str(path);
|
||||
for (auto i = params.begin(); i != params.end(); ++i) {
|
||||
rule->add_param(std::get<0>(*i), std::get<1>(*i));
|
||||
}
|
||||
_routes.add(rule, operations.method);
|
||||
}
|
||||
}
|
||||
|
||||
void path_description::set(routes& _routes,
|
||||
const json_request_function& f) const {
|
||||
set(_routes, new function_handler(f));
|
||||
}
|
||||
|
||||
void path_description::set(routes& _routes, const future_json_function& f) const {
|
||||
set(_routes, new function_handler(f));
|
||||
}
|
||||
path_description::path_description(const sstring& path, operation_type method,
|
||||
const sstring& nickname,
|
||||
const std::vector<std::pair<sstring, bool>>& path_parameters,
|
||||
const std::vector<sstring>& mandatory_params)
|
||||
: path(path), operations(method, nickname) {
|
||||
|
||||
for (auto man : mandatory_params) {
|
||||
pushmandatory_param(man);
|
||||
}
|
||||
for (auto param : path_parameters) {
|
||||
params.push_back(param);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef JSON_PATH_HH_
|
||||
#define JSON_PATH_HH_
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <tuple>
|
||||
#include "common.hh"
|
||||
#include "core/sstring.hh"
|
||||
#include "routes.hh"
|
||||
#include "function_handlers.hh"
|
||||
|
||||
namespace httpd {
|
||||
|
||||
/**
|
||||
* A json_operation contain a method and a nickname.
|
||||
* operation are associated to a path, that can
|
||||
* have multiple methods
|
||||
*/
|
||||
struct json_operation {
|
||||
/**
|
||||
* default constructor
|
||||
*/
|
||||
json_operation()
|
||||
: method(GET) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with assignment
|
||||
* @param method the http method type
|
||||
* @param nickname the http nickname
|
||||
*/
|
||||
json_operation(operation_type method, const sstring& nickname)
|
||||
: method(method), nickname(nickname) {
|
||||
}
|
||||
|
||||
operation_type method;
|
||||
sstring nickname;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* path description holds the path in the system.
|
||||
* It maps a nickname to an operation, which allows
|
||||
* defining the operation (path and method) by its
|
||||
* nickname.
|
||||
*
|
||||
* the description are taken from the json swagger
|
||||
* definition file, during auto code generation in the
|
||||
* compilation.
|
||||
*/
|
||||
struct path_description {
|
||||
/**
|
||||
* default empty constructor
|
||||
*/
|
||||
path_description() = default;
|
||||
|
||||
/**
|
||||
* constructor for path with parameters
|
||||
* The constructor is used by
|
||||
* @param path the url path
|
||||
* @param method the http method
|
||||
* @param nickname the nickname
|
||||
*/
|
||||
path_description(const sstring& path, operation_type method,
|
||||
const sstring& nickname,
|
||||
const std::vector<std::pair<sstring, bool>>& path_parameters,
|
||||
const std::vector<sstring>& mandatory_params);
|
||||
|
||||
/**
|
||||
* Add a parameter to the path definition
|
||||
* for example, if the url should match /file/{path}
|
||||
* The constructor would be followed by a call to
|
||||
* pushparam("path")
|
||||
*
|
||||
* @param param the name of the parameters, this name will
|
||||
* be used by the handler to identify the parameters.
|
||||
* A name can appear at most once in a description
|
||||
* @param all_path when set to true the parameter will assume to match
|
||||
* until the end of the url.
|
||||
* This is useful for situation like file path with
|
||||
* a rule like /file/{path} and a url /file/etc/hosts.
|
||||
* path should be equal to /ets/hosts and not only /etc
|
||||
* @return the current path description
|
||||
*/
|
||||
path_description* pushparam(const sstring& param,
|
||||
bool all_path = false) {
|
||||
params.push_back( { param, all_path });
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a mandatory query parameter to the path
|
||||
* this parameter will be check before calling a handler
|
||||
* @param param the parameter to head
|
||||
* @return a pointer to the current path description
|
||||
*/
|
||||
path_description* pushmandatory_param(const sstring& param) {
|
||||
mandatory_queryparams.push_back(param);
|
||||
return this;
|
||||
}
|
||||
|
||||
std::vector<std::pair<sstring, bool>> params;
|
||||
sstring path;
|
||||
json_operation operations;
|
||||
|
||||
std::vector<sstring> mandatory_queryparams;
|
||||
|
||||
void set(routes& _routes, handler_base* handler) const;
|
||||
|
||||
void set(routes& _routes, const json_request_function& f) const;
|
||||
|
||||
void set(routes& _routes, const future_json_function& f) const;
|
||||
};
|
||||
|
||||
}
|
||||
#endif /* JSON_PATH_HH_ */
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#include "matcher.hh"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace httpd {
|
||||
|
||||
using namespace std;
|
||||
|
||||
/**
|
||||
* Search for the end of the url parameter.
|
||||
* @param url the url to search
|
||||
* @param ind the position in the url
|
||||
* @param entire_path when set to true, take all the reminaing url
|
||||
* when set to false, search for the next slash
|
||||
* @return the position in the url of the end of the parameter
|
||||
*/
|
||||
static size_t find_end_param(const sstring& url, size_t ind, bool entire_path) {
|
||||
size_t pos = (entire_path) ? url.length() : url.find('/', ind + 1);
|
||||
if (pos == sstring::npos) {
|
||||
return url.length();
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
size_t param_matcher::match(const sstring& url, size_t ind, parameters& param) {
|
||||
size_t last = find_end_param(url, ind, _entire_path);
|
||||
if (last == ind) {
|
||||
/*
|
||||
* empty parameter allows only for the case of entire_path
|
||||
*/
|
||||
if (_entire_path) {
|
||||
param.set(_name, "");
|
||||
return ind;
|
||||
}
|
||||
return sstring::npos;
|
||||
}
|
||||
param.set(_name, url.substr(ind, last - ind));
|
||||
return last;
|
||||
}
|
||||
|
||||
size_t str_matcher::match(const sstring& url, size_t ind, parameters& param) {
|
||||
if (url.length() >= _len + ind && (url.find(_cmp, ind) == ind)
|
||||
&& (url.length() == _len + ind || url.at(_len + ind) == '/')) {
|
||||
return _len + ind;
|
||||
}
|
||||
return sstring::npos;
|
||||
}
|
||||
|
||||
}
|
||||
110
http/matcher.hh
110
http/matcher.hh
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef MATCHER_HH_
|
||||
#define MATCHER_HH_
|
||||
|
||||
#include "common.hh"
|
||||
|
||||
#include "core/sstring.hh"
|
||||
|
||||
namespace httpd {
|
||||
|
||||
/**
|
||||
* a base class for the url matching.
|
||||
* Each implementation check if the given url matches a criteria
|
||||
*/
|
||||
class matcher {
|
||||
public:
|
||||
|
||||
virtual ~matcher() = default;
|
||||
|
||||
/**
|
||||
* check if the given url matches the rule
|
||||
* @param url the url to check
|
||||
* @param ind the position to start from
|
||||
* @param fill the parameters hash
|
||||
* @return the end of of the matched part, or sstring::npos if not matched
|
||||
*/
|
||||
virtual size_t match(const sstring& url, size_t ind, parameters& param) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the url match a parameter and fill the parameters object
|
||||
*
|
||||
* Note that a non empty url will always return true with the parameters
|
||||
* object filled
|
||||
*
|
||||
* Assume that the rule is /file/{path}/ and the param_matcher identify
|
||||
* the /{path}
|
||||
*
|
||||
* For all non empty values, match will return true.
|
||||
* If the entire url is /file/etc/hosts, and the part that is passed to
|
||||
* param_matcher is /etc/hosts, if entire_path is true, the match will be
|
||||
* '/etc/hosts' If entire_path is false, the match will be '/etc'
|
||||
*/
|
||||
class param_matcher : public matcher {
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
* @param name the name of the parameter, will be used as the key
|
||||
* in the parameters object
|
||||
* @param entire_path when set to true, the matched parameters will
|
||||
* include all the remaining url until the end of it.
|
||||
* when set to false the match will terminate at the next slash
|
||||
*/
|
||||
explicit param_matcher(const sstring& name, bool entire_path = false)
|
||||
: _name(name), _entire_path(entire_path) {
|
||||
}
|
||||
|
||||
virtual size_t match(const sstring& url, size_t ind, parameters& param)
|
||||
override;
|
||||
private:
|
||||
sstring _name;
|
||||
bool _entire_path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the url match a predefine string.
|
||||
*
|
||||
* When parsing a match rule such as '/file/{path}' the str_match would parse
|
||||
* the '/file' part
|
||||
*/
|
||||
class str_matcher : public matcher {
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
* @param cmp the string to match
|
||||
*/
|
||||
explicit str_matcher(const sstring& cmp)
|
||||
: _cmp(cmp), _len(cmp.size()) {
|
||||
}
|
||||
|
||||
virtual size_t match(const sstring& url, size_t ind, parameters& param)
|
||||
override;
|
||||
private:
|
||||
sstring _cmp;
|
||||
unsigned _len;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* MATCHER_HH_ */
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* This file is open source software, licensed to you under the terms
|
||||
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
||||
* distributed with this work for additional information regarding copyright
|
||||
* ownership. You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Cloudius Systems
|
||||
*/
|
||||
|
||||
#ifndef MATCH_RULES_HH_
|
||||
#define MATCH_RULES_HH_
|
||||
|
||||
#include "handlers.hh"
|
||||
#include "matcher.hh"
|
||||
#include "common.hh"
|
||||
|
||||
#include "core/sstring.hh"
|
||||
#include <vector>
|
||||
|
||||
namespace httpd {
|
||||
|
||||
/**
|
||||
* match_rule check if a url matches criteria, that can contains
|
||||
* parameters.
|
||||
* the routes object would call the get method with a url and if
|
||||
* it matches, the method will return a handler
|
||||
* during the matching process, the method fill the parameters object.
|
||||
*/
|
||||
class match_rule {
|
||||
public:
|
||||
/**
|
||||
* The destructor deletes matchers.
|
||||
*/
|
||||
~match_rule() {
|
||||
for (auto m : _match_list) {
|
||||
delete m;
|
||||
}
|
||||
delete _handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with a handler
|
||||
* @param handler the handler to return when this match rule is met
|
||||
*/
|
||||
explicit match_rule(handler_base* handler)
|
||||
: _handler(handler) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if url match the rule and return a handler if it does
|
||||
* @param url a url to compare against the rule
|
||||
* @param params the parameters object, matches parameters will fill
|
||||
* the object during the matching process
|
||||
* @return a handler if there is a full match or nullptr if not
|
||||
*/
|
||||
handler_base* get(const sstring& url, parameters& params) {
|
||||
size_t ind = 0;
|
||||
for (unsigned int i = 0; i < _match_list.size(); i++) {
|
||||
ind = _match_list.at(i)->match(url, ind, params);
|
||||
if (ind == sstring::npos) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return (ind + 1 >= url.length()) ? _handler : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a matcher to the rule
|
||||
* @param match the matcher to add
|
||||
* @return this
|
||||
*/
|
||||
match_rule& add_matcher(matcher* match) {
|
||||
_match_list.push_back(match);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a static url matcher
|
||||
* @param str the string to search for
|
||||
* @return this
|
||||
*/
|
||||
match_rule& add_str(const sstring& str) {
|
||||
add_matcher(new str_matcher(str));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* add a parameter matcher to the rule
|
||||
* @param str the parameter name
|
||||
* @param fullpath when set to true, parameter will included all the
|
||||
* remaining url until its end
|
||||
* @return this
|
||||
*/
|
||||
match_rule& add_param(const sstring& str, bool fullpath = false) {
|
||||
add_matcher(new param_matcher(str, fullpath));
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<matcher*> _match_list;
|
||||
handler_base* _handler;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* MATCH_RULES_HH_ */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user