Compare commits
115 Commits
Author | SHA1 | Date | |
---|---|---|---|
70632d975d | |||
d210a39249 | |||
e86ff63f91 | |||
3454a1965a | |||
c41fa2e98f | |||
a012ca4003 | |||
b74e5aa4ee | |||
44ac15ab78 | |||
b1b652b943 | |||
530a482402 | |||
f9023cf0ab | |||
0e91ea6786 | |||
63c6226a2a | |||
48a49c2a2d | |||
43c28b749d | |||
241767df13 | |||
b2baba9237 | |||
3e36ec0984 | |||
5be89e4858 | |||
a48d9cb739 | |||
a7206cbd02 | |||
1450a6adfe | |||
5fa9bd7b49 | |||
c7eb233fc7 | |||
20c8b659d1 | |||
4ea9c2b740 | |||
48205b2fc9 | |||
f5fe7bec4a | |||
c567b88d5d | |||
fe884541fc | |||
8973bdb2af | |||
4c4e65b373 | |||
7bed7f039f | |||
8ddea0aa46 | |||
cb6cc65113 | |||
4de42ca227 | |||
ab930657b6 | |||
291920b479 | |||
999c9bdb0f | |||
405b6d9f9f | |||
87b74b2542 | |||
d6514cb515 | |||
976eb4bfbc | |||
fb843e3473 | |||
f3d48234c0 | |||
254b881da6 | |||
8e0b742748 | |||
a35e04c049 | |||
4271c8583e | |||
e27d0fe58c | |||
67952dabdb | |||
b8d5d4c2bd | |||
b6413823cd | |||
48e355b87f | |||
b3f4ea572e | |||
c73e704421 | |||
0561e96f21 | |||
86d87430da | |||
0156c5a883 | |||
c966b42ac3 | |||
200fff3714 | |||
28755b4d01 | |||
8bd6a14988 | |||
b41dcc9f37 | |||
38addb7a5b | |||
f7fb89fed0 | |||
e32af2d09e | |||
f1bc4dc2e2 | |||
40857e69b7 | |||
c1fb89af99 | |||
061cd6264b | |||
f437dafa6b | |||
54d0597657 | |||
ab9f96e0c7 | |||
711855474c | |||
b20f367aa8 | |||
a2dadda511 | |||
77dca31261 | |||
b87aed4395 | |||
42bbb3b023 | |||
4309a30dfe | |||
9362287938 | |||
78bd901339 | |||
c8e6d44f7b | |||
f75433e0e6 | |||
fa607141e4 | |||
b3fdd6fd4a | |||
86c08e7af6 | |||
1c5796eb96 | |||
f7f92e7906 | |||
1123d01e6c | |||
c53d319337 | |||
7c36dbb8f0 | |||
dd3becf6b7 | |||
b78ecdf4c5 | |||
a4aa5bcb2e | |||
edd3ec4b32 | |||
9fdcef86e7 | |||
ed0eb4ac74 | |||
192ee20bf7 | |||
965ca0088e | |||
b752acdff7 | |||
cbeb0395f9 | |||
5e6f8446d8 | |||
79a7b840f7 | |||
6b093cd5fa | |||
154e2f2ff7 | |||
e9a0a93d3c | |||
da5dc276d5 | |||
721bb110e5 | |||
9241ec503c | |||
c2afb07ff6 | |||
698660c4c8 | |||
be9181698a | |||
25c292662a |
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,2 +1,11 @@
|
|||||||
|
# Binary
|
||||||
|
*.[oa]
|
||||||
|
|
||||||
|
# D
|
||||||
.dub
|
.dub
|
||||||
__test__*__
|
__test__*__
|
||||||
|
__test__*__.core
|
||||||
|
/tanya-test-library
|
||||||
|
|
||||||
|
/docs/
|
||||||
|
/docs.json
|
||||||
|
20
.travis.yml
Normal file
20
.travis.yml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
sudo: false
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
|
language: d
|
||||||
|
|
||||||
|
d:
|
||||||
|
- dmd-2.073.0
|
||||||
|
- dmd-2.072.2
|
||||||
|
- dmd-2.071.2
|
||||||
|
- dmd-2.070.2
|
||||||
|
|
||||||
|
env:
|
||||||
|
matrix:
|
||||||
|
- ARCH=x86_64
|
||||||
|
|
||||||
|
script:
|
||||||
|
- dub test --arch=$ARCH
|
66
README.md
Normal file
66
README.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Tanya
|
||||||
|
|
||||||
|
[](https://travis-ci.org/caraus-ecms/tanya)
|
||||||
|
[](https://code.dlang.org/packages/tanya)
|
||||||
|
[](https://code.dlang.org/packages/tanya)
|
||||||
|
[](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
|
||||||
|
|
||||||
|
Tanya is a general purpose library for D programming language.
|
||||||
|
|
||||||
|
Its aim is to simplify the manual memory management in D and to provide a
|
||||||
|
guarantee with @nogc attribute that there are no hidden allocations on the
|
||||||
|
Garbage Collector heap. Everything in the library is usable in @nogc code.
|
||||||
|
Tanya extends Phobos functionality and provides alternative implementations for
|
||||||
|
data structures and utilities that depend on the Garbage Collector in Phobos.
|
||||||
|
|
||||||
|
* [Bug tracker](https://issues.caraus.io/projects/tanya)
|
||||||
|
* [Documentation](https://docs.caraus.io/tanya)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Tanya consists of the following packages:
|
||||||
|
|
||||||
|
* `async`: Event loop (epoll, kqueue and IOCP).
|
||||||
|
* `container`: Queue, Vector, Singly linked list, buffers.
|
||||||
|
* `math`: Arbitrary precision integer and a set of functions.
|
||||||
|
* `memory`: Tools for manual memory management (allocator, reference counting,
|
||||||
|
helper functions).
|
||||||
|
* `network`: URL-Parsing, sockets.
|
||||||
|
|
||||||
|
### Supported compilers
|
||||||
|
|
||||||
|
* dmd 2.073.0
|
||||||
|
* dmd 2.072.2
|
||||||
|
* dmd 2.071.2
|
||||||
|
* dmd 2.070.2
|
||||||
|
|
||||||
|
### Current status
|
||||||
|
|
||||||
|
The library is currently under development, but the API is becoming gradually
|
||||||
|
stable.
|
||||||
|
|
||||||
|
`container`s are being extended to support ranges. Also following modules are
|
||||||
|
coming soon:
|
||||||
|
* UTF-8 string.
|
||||||
|
* Hash table.
|
||||||
|
|
||||||
|
`math` package contains an arbitrary precision integer implementation that
|
||||||
|
needs more test cases, better performance and some additional features
|
||||||
|
(constructing from a string and an ubyte array, and converting it back).
|
||||||
|
|
||||||
|
### Further characteristics
|
||||||
|
|
||||||
|
* Tanya is a native D library.
|
||||||
|
|
||||||
|
* Tanya is cross-platform. The development happens on a 64-bit Linux, but it
|
||||||
|
is being tested on Windows and FreeBSD as well.
|
||||||
|
|
||||||
|
* The library isn't thread-safe. Thread-safity should be added later.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Since I'm mostly busy writing new code and implementing new features I would
|
||||||
|
appreciate, if anyone uses the library. It would help me to improve the
|
||||||
|
codebase and fix issues.
|
||||||
|
|
||||||
|
Feel free to contact me if you have any questions.
|
2
dub.json
2
dub.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tanya",
|
"name": "tanya",
|
||||||
"description": "D library with event loop",
|
"description": "General purpose, @nogc library",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"copyright": "(c) Eugene Wissner <info@caraus.de>",
|
"copyright": "(c) Eugene Wissner <info@caraus.de>",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
181
source/tanya/async/event/epoll.d
Normal file
181
source/tanya/async/event/epoll.d
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
*/
|
||||||
|
module tanya.async.event.epoll;
|
||||||
|
|
||||||
|
version (linux):
|
||||||
|
|
||||||
|
public import core.sys.linux.epoll;
|
||||||
|
import tanya.async.protocol;
|
||||||
|
import tanya.async.event.selector;
|
||||||
|
import tanya.async.loop;
|
||||||
|
import tanya.async.transport;
|
||||||
|
import tanya.async.watcher;
|
||||||
|
import tanya.container.vector;
|
||||||
|
import tanya.memory;
|
||||||
|
import tanya.memory.mmappool;
|
||||||
|
import tanya.network.socket;
|
||||||
|
import core.stdc.errno;
|
||||||
|
import core.sys.posix.unistd;
|
||||||
|
import core.time;
|
||||||
|
import std.algorithm.comparison;
|
||||||
|
|
||||||
|
extern (C) nothrow @nogc
|
||||||
|
{
|
||||||
|
int epoll_create1(int flags);
|
||||||
|
int epoll_ctl (int epfd, int op, int fd, epoll_event *event);
|
||||||
|
int epoll_wait (int epfd, epoll_event *events, int maxevents, int timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class EpollLoop : SelectorLoop
|
||||||
|
{
|
||||||
|
protected int fd;
|
||||||
|
private Vector!epoll_event events;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the loop.
|
||||||
|
*/
|
||||||
|
this() @nogc
|
||||||
|
{
|
||||||
|
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
|
||||||
|
{
|
||||||
|
throw defaultAllocator.make!BadLoopException("epoll initialization failed");
|
||||||
|
}
|
||||||
|
super();
|
||||||
|
events = Vector!epoll_event(maxEvents, MmapPool.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees loop internals.
|
||||||
|
*/
|
||||||
|
~this() @nogc
|
||||||
|
{
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be called if the backend configuration changes.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* watcher = Watcher.
|
||||||
|
* oldEvents = The events were already set.
|
||||||
|
* events = The events should be set.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
|
*/
|
||||||
|
protected override bool reify(SocketWatcher watcher,
|
||||||
|
EventMask oldEvents,
|
||||||
|
EventMask events) @nogc
|
||||||
|
{
|
||||||
|
int op = EPOLL_CTL_DEL;
|
||||||
|
epoll_event ev;
|
||||||
|
|
||||||
|
if (events == oldEvents)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (events && oldEvents)
|
||||||
|
{
|
||||||
|
op = EPOLL_CTL_MOD;
|
||||||
|
}
|
||||||
|
else if (events && !oldEvents)
|
||||||
|
{
|
||||||
|
op = EPOLL_CTL_ADD;
|
||||||
|
}
|
||||||
|
|
||||||
|
ev.data.fd = watcher.socket.handle;
|
||||||
|
ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0)
|
||||||
|
| (events & Event.write ? EPOLLOUT : 0)
|
||||||
|
| EPOLLET;
|
||||||
|
|
||||||
|
return epoll_ctl(fd, op, watcher.socket.handle, &ev) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the actual polling.
|
||||||
|
*/
|
||||||
|
protected override void poll() @nogc
|
||||||
|
{
|
||||||
|
// Don't block
|
||||||
|
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
||||||
|
auto eventCount = epoll_wait(fd, events.get().ptr, maxEvents, timeout);
|
||||||
|
|
||||||
|
if (eventCount < 0)
|
||||||
|
{
|
||||||
|
if (errno != EINTR)
|
||||||
|
{
|
||||||
|
throw defaultAllocator.make!BadLoopException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = 0; i < eventCount; ++i)
|
||||||
|
{
|
||||||
|
auto transport = cast(StreamTransport) connections[events[i].data.fd];
|
||||||
|
|
||||||
|
if (transport is null)
|
||||||
|
{
|
||||||
|
auto connection = cast(ConnectionWatcher) connections[events[i].data.fd];
|
||||||
|
assert(connection !is null);
|
||||||
|
|
||||||
|
acceptConnections(connection);
|
||||||
|
}
|
||||||
|
else if (events[i].events & EPOLLERR)
|
||||||
|
{
|
||||||
|
kill(transport);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP))
|
||||||
|
{
|
||||||
|
SocketException exception;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ptrdiff_t received;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
received = transport.socket.receive(transport.output[]);
|
||||||
|
transport.output += received;
|
||||||
|
}
|
||||||
|
while (received);
|
||||||
|
}
|
||||||
|
catch (SocketException e)
|
||||||
|
{
|
||||||
|
exception = e;
|
||||||
|
}
|
||||||
|
if (transport.socket.disconnected)
|
||||||
|
{
|
||||||
|
kill(transport, exception);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (transport.output.length)
|
||||||
|
{
|
||||||
|
pendings.enqueue(transport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (events[i].events & EPOLLOUT)
|
||||||
|
{
|
||||||
|
transport.writeReady = true;
|
||||||
|
if (transport.input.length)
|
||||||
|
{
|
||||||
|
feed(transport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: The blocking time.
|
||||||
|
*/
|
||||||
|
override protected @property inout(Duration) blockTime()
|
||||||
|
inout @safe pure nothrow
|
||||||
|
{
|
||||||
|
return min(super.blockTime, 1.dur!"seconds");
|
||||||
|
}
|
||||||
|
}
|
384
source/tanya/async/event/iocp.d
Normal file
384
source/tanya/async/event/iocp.d
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
*/
|
||||||
|
module tanya.async.event.iocp;
|
||||||
|
|
||||||
|
version (Windows):
|
||||||
|
|
||||||
|
import tanya.container.buffer;
|
||||||
|
import tanya.async.loop;
|
||||||
|
import tanya.async.protocol;
|
||||||
|
import tanya.async.transport;
|
||||||
|
import tanya.async.watcher;
|
||||||
|
import tanya.memory;
|
||||||
|
import tanya.memory.mmappool;
|
||||||
|
import tanya.network.socket;
|
||||||
|
import core.sys.windows.basetyps;
|
||||||
|
import core.sys.windows.mswsock;
|
||||||
|
import core.sys.windows.winbase;
|
||||||
|
import core.sys.windows.windef;
|
||||||
|
import core.sys.windows.winsock2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transport for stream sockets.
|
||||||
|
*/
|
||||||
|
final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
||||||
|
{
|
||||||
|
private SocketException exception;
|
||||||
|
|
||||||
|
private ReadBuffer!ubyte output;
|
||||||
|
|
||||||
|
private WriteBuffer!ubyte input;
|
||||||
|
|
||||||
|
private Protocol protocol_;
|
||||||
|
|
||||||
|
private bool closing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new completion port transport.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* socket = Socket.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE socket !is null)
|
||||||
|
*/
|
||||||
|
this(OverlappedConnectedSocket socket) @nogc
|
||||||
|
{
|
||||||
|
super(socket);
|
||||||
|
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
|
||||||
|
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Socket.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE socket !is null)
|
||||||
|
*/
|
||||||
|
override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc
|
||||||
|
out (socket)
|
||||||
|
{
|
||||||
|
assert(socket !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return cast(OverlappedConnectedSocket) socket_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns $(D_PARAM true) if the transport is closing or closed.
|
||||||
|
*/
|
||||||
|
bool isClosing() const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return closing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the transport.
|
||||||
|
*
|
||||||
|
* Buffered data will be flushed. No more data will be received.
|
||||||
|
*/
|
||||||
|
void close() pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
closing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write some data to the transport.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* data = Data to send.
|
||||||
|
*/
|
||||||
|
void write(ubyte[] data) @nogc
|
||||||
|
{
|
||||||
|
input ~= data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Application protocol.
|
||||||
|
*/
|
||||||
|
@property Protocol protocol() pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return protocol_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches the protocol.
|
||||||
|
*
|
||||||
|
* The protocol is deallocated by the event loop, it should currently be
|
||||||
|
* allocated with $(D_PSYMBOL MmapPool).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* protocol = Application protocol.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE protocol !is null)
|
||||||
|
*/
|
||||||
|
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(protocol !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
protocol_ = protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the watcher callback.
|
||||||
|
*/
|
||||||
|
override void invoke() @nogc
|
||||||
|
{
|
||||||
|
if (output.length)
|
||||||
|
{
|
||||||
|
immutable empty = input.length == 0;
|
||||||
|
protocol.received(output[0 .. $]);
|
||||||
|
output.clear();
|
||||||
|
if (empty)
|
||||||
|
{
|
||||||
|
SocketState overlapped;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
overlapped = MmapPool.instance.make!SocketState;
|
||||||
|
socket.beginSend(input[], overlapped);
|
||||||
|
}
|
||||||
|
catch (SocketException e)
|
||||||
|
{
|
||||||
|
MmapPool.instance.dispose(overlapped);
|
||||||
|
MmapPool.instance.dispose(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
protocol.disconnected(exception);
|
||||||
|
MmapPool.instance.dispose(protocol_);
|
||||||
|
defaultAllocator.dispose(exception);
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class IOCPLoop : Loop
|
||||||
|
{
|
||||||
|
protected HANDLE completionPort;
|
||||||
|
|
||||||
|
protected OVERLAPPED overlap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the loop.
|
||||||
|
*/
|
||||||
|
this() @nogc
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
|
||||||
|
if (!completionPort)
|
||||||
|
{
|
||||||
|
throw make!BadLoopException(defaultAllocator,
|
||||||
|
"Creating completion port failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be called if the backend configuration changes.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* watcher = Watcher.
|
||||||
|
* oldEvents = The events were already set.
|
||||||
|
* events = The events should be set.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
|
*/
|
||||||
|
override protected bool reify(SocketWatcher watcher,
|
||||||
|
EventMask oldEvents,
|
||||||
|
EventMask events) @nogc
|
||||||
|
{
|
||||||
|
SocketState overlapped;
|
||||||
|
if (!(oldEvents & Event.accept) && (events & Event.accept))
|
||||||
|
{
|
||||||
|
auto socket = cast(OverlappedStreamSocket) watcher.socket;
|
||||||
|
assert(socket !is null);
|
||||||
|
|
||||||
|
if (CreateIoCompletionPort(cast(HANDLE) socket.handle,
|
||||||
|
completionPort,
|
||||||
|
cast(ULONG_PTR) (cast(void*) watcher),
|
||||||
|
0) !is completionPort)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
overlapped = MmapPool.instance.make!SocketState;
|
||||||
|
socket.beginAccept(overlapped);
|
||||||
|
}
|
||||||
|
catch (SocketException e)
|
||||||
|
{
|
||||||
|
MmapPool.instance.dispose(overlapped);
|
||||||
|
defaultAllocator.dispose(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(oldEvents & Event.read) && (events & Event.read)
|
||||||
|
|| !(oldEvents & Event.write) && (events & Event.write))
|
||||||
|
{
|
||||||
|
auto transport = cast(StreamTransport) watcher;
|
||||||
|
assert(transport !is null);
|
||||||
|
|
||||||
|
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
|
||||||
|
completionPort,
|
||||||
|
cast(ULONG_PTR) (cast(void*) watcher),
|
||||||
|
0) !is completionPort)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin to read
|
||||||
|
if (!(oldEvents & Event.read) && (events & Event.read))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
overlapped = MmapPool.instance.make!SocketState;
|
||||||
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
|
}
|
||||||
|
catch (SocketException e)
|
||||||
|
{
|
||||||
|
MmapPool.instance.dispose(overlapped);
|
||||||
|
defaultAllocator.dispose(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void kill(StreamTransport transport,
|
||||||
|
SocketException exception = null) @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(transport !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
transport.socket.shutdown();
|
||||||
|
defaultAllocator.dispose(transport.socket);
|
||||||
|
transport.exception = exception;
|
||||||
|
pendings.enqueue(transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the actual polling.
|
||||||
|
*/
|
||||||
|
override protected void poll() @nogc
|
||||||
|
{
|
||||||
|
DWORD lpNumberOfBytes;
|
||||||
|
ULONG_PTR key;
|
||||||
|
LPOVERLAPPED overlap;
|
||||||
|
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
||||||
|
|
||||||
|
auto result = GetQueuedCompletionStatus(completionPort,
|
||||||
|
&lpNumberOfBytes,
|
||||||
|
&key,
|
||||||
|
&overlap,
|
||||||
|
timeout);
|
||||||
|
if (result == FALSE && overlap == NULL)
|
||||||
|
{
|
||||||
|
return; // Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8));
|
||||||
|
assert(overlapped !is null);
|
||||||
|
scope (failure)
|
||||||
|
{
|
||||||
|
MmapPool.instance.dispose(overlapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (overlapped.event)
|
||||||
|
{
|
||||||
|
case OverlappedSocketEvent.accept:
|
||||||
|
auto connection = cast(ConnectionWatcher) (cast(void*) key);
|
||||||
|
assert(connection !is null);
|
||||||
|
|
||||||
|
auto listener = cast(OverlappedStreamSocket) connection.socket;
|
||||||
|
assert(listener !is null);
|
||||||
|
|
||||||
|
auto socket = listener.endAccept(overlapped);
|
||||||
|
auto transport = MmapPool.instance.make!StreamTransport(socket);
|
||||||
|
|
||||||
|
connection.incoming.enqueue(transport);
|
||||||
|
|
||||||
|
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||||
|
|
||||||
|
pendings.enqueue(connection);
|
||||||
|
listener.beginAccept(overlapped);
|
||||||
|
break;
|
||||||
|
case OverlappedSocketEvent.read:
|
||||||
|
auto transport = cast(StreamTransport) (cast(void*) key);
|
||||||
|
assert(transport !is null);
|
||||||
|
|
||||||
|
if (!transport.active)
|
||||||
|
{
|
||||||
|
MmapPool.instance.dispose(transport);
|
||||||
|
MmapPool.instance.dispose(overlapped);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int received;
|
||||||
|
SocketException exception;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
received = transport.socket.endReceive(overlapped);
|
||||||
|
}
|
||||||
|
catch (SocketException e)
|
||||||
|
{
|
||||||
|
exception = e;
|
||||||
|
}
|
||||||
|
if (transport.socket.disconnected)
|
||||||
|
{
|
||||||
|
// We want to get one last notification to destroy the watcher.
|
||||||
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
|
kill(transport, exception);
|
||||||
|
}
|
||||||
|
else if (received > 0)
|
||||||
|
{
|
||||||
|
immutable full = transport.output.free == received;
|
||||||
|
|
||||||
|
transport.output += received;
|
||||||
|
// Receive was interrupted because the buffer is full. We have to continue.
|
||||||
|
if (full)
|
||||||
|
{
|
||||||
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
|
}
|
||||||
|
pendings.enqueue(transport);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OverlappedSocketEvent.write:
|
||||||
|
auto transport = cast(StreamTransport) (cast(void*) key);
|
||||||
|
assert(transport !is null);
|
||||||
|
|
||||||
|
transport.input += transport.socket.endSend(overlapped);
|
||||||
|
if (transport.input.length > 0)
|
||||||
|
{
|
||||||
|
transport.socket.beginSend(transport.input[], overlapped);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
|
if (transport.isClosing())
|
||||||
|
{
|
||||||
|
kill(transport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false, "Unknown event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
325
source/tanya/async/event/kqueue.d
Normal file
325
source/tanya/async/event/kqueue.d
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
*/
|
||||||
|
module tanya.async.event.kqueue;
|
||||||
|
|
||||||
|
version (OSX)
|
||||||
|
{
|
||||||
|
version = MacBSD;
|
||||||
|
}
|
||||||
|
else version (iOS)
|
||||||
|
{
|
||||||
|
version = MacBSD;
|
||||||
|
}
|
||||||
|
else version (TVOS)
|
||||||
|
{
|
||||||
|
version = MacBSD;
|
||||||
|
}
|
||||||
|
else version (WatchOS)
|
||||||
|
{
|
||||||
|
version = MacBSD;
|
||||||
|
}
|
||||||
|
else version (FreeBSD)
|
||||||
|
{
|
||||||
|
version = MacBSD;
|
||||||
|
}
|
||||||
|
else version (OpenBSD)
|
||||||
|
{
|
||||||
|
version = MacBSD;
|
||||||
|
}
|
||||||
|
else version (DragonFlyBSD)
|
||||||
|
{
|
||||||
|
version = MacBSD;
|
||||||
|
}
|
||||||
|
|
||||||
|
version (MacBSD):
|
||||||
|
|
||||||
|
import core.stdc.errno;
|
||||||
|
import core.sys.posix.time; // timespec
|
||||||
|
import core.sys.posix.unistd;
|
||||||
|
import core.time;
|
||||||
|
import std.algorithm.comparison;
|
||||||
|
import tanya.async.event.selector;
|
||||||
|
import tanya.async.loop;
|
||||||
|
import tanya.async.transport;
|
||||||
|
import tanya.async.watcher;
|
||||||
|
import tanya.container.vector;
|
||||||
|
import tanya.memory;
|
||||||
|
import tanya.memory.mmappool;
|
||||||
|
import tanya.network.socket;
|
||||||
|
|
||||||
|
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
|
||||||
|
{
|
||||||
|
*kevp = kevent_t(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum : short
|
||||||
|
{
|
||||||
|
EVFILT_READ = -1,
|
||||||
|
EVFILT_WRITE = -2,
|
||||||
|
EVFILT_AIO = -3, /* attached to aio requests */
|
||||||
|
EVFILT_VNODE = -4, /* attached to vnodes */
|
||||||
|
EVFILT_PROC = -5, /* attached to struct proc */
|
||||||
|
EVFILT_SIGNAL = -6, /* attached to struct proc */
|
||||||
|
EVFILT_TIMER = -7, /* timers */
|
||||||
|
EVFILT_MACHPORT = -8, /* Mach portsets */
|
||||||
|
EVFILT_FS = -9, /* filesystem events */
|
||||||
|
EVFILT_USER = -10, /* User events */
|
||||||
|
EVFILT_VM = -12, /* virtual memory events */
|
||||||
|
EVFILT_SYSCOUNT = 11
|
||||||
|
}
|
||||||
|
|
||||||
|
struct kevent_t
|
||||||
|
{
|
||||||
|
uintptr_t ident; /* identifier for this event */
|
||||||
|
short filter; /* filter for event */
|
||||||
|
ushort flags;
|
||||||
|
uint fflags;
|
||||||
|
intptr_t data;
|
||||||
|
void *udata; /* opaque user data identifier */
|
||||||
|
}
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
/* actions */
|
||||||
|
EV_ADD = 0x0001, /* add event to kq (implies enable) */
|
||||||
|
EV_DELETE = 0x0002, /* delete event from kq */
|
||||||
|
EV_ENABLE = 0x0004, /* enable event */
|
||||||
|
EV_DISABLE = 0x0008, /* disable event (not reported) */
|
||||||
|
|
||||||
|
/* flags */
|
||||||
|
EV_ONESHOT = 0x0010, /* only report one occurrence */
|
||||||
|
EV_CLEAR = 0x0020, /* clear event state after reporting */
|
||||||
|
EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */
|
||||||
|
EV_DISPATCH = 0x0080, /* disable event after reporting */
|
||||||
|
|
||||||
|
EV_SYSFLAGS = 0xF000, /* reserved by system */
|
||||||
|
EV_FLAG1 = 0x2000, /* filter-specific flag */
|
||||||
|
|
||||||
|
/* returned values */
|
||||||
|
EV_EOF = 0x8000, /* EOF detected */
|
||||||
|
EV_ERROR = 0x4000, /* error, data contains errno */
|
||||||
|
}
|
||||||
|
|
||||||
|
extern(C) int kqueue() nothrow @nogc;
|
||||||
|
extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
|
||||||
|
kevent_t *eventlist, int nevents, const timespec *timeout)
|
||||||
|
nothrow @nogc;
|
||||||
|
|
||||||
|
final class KqueueLoop : SelectorLoop
|
||||||
|
{
|
||||||
|
protected int fd;
|
||||||
|
private Vector!kevent_t events;
|
||||||
|
private Vector!kevent_t changes;
|
||||||
|
private size_t changeCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Maximal event count can be got at a time
|
||||||
|
* (should be supported by the backend).
|
||||||
|
*/
|
||||||
|
override protected @property uint maxEvents()
|
||||||
|
const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return cast(uint) events.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
this() @nogc
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
if ((fd = kqueue()) == -1)
|
||||||
|
{
|
||||||
|
throw make!BadLoopException(defaultAllocator,
|
||||||
|
"kqueue initialization failed");
|
||||||
|
}
|
||||||
|
events = Vector!kevent_t(64, MmapPool.instance);
|
||||||
|
changes = Vector!kevent_t(64, MmapPool.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees loop internals.
|
||||||
|
*/
|
||||||
|
~this() @nogc
|
||||||
|
{
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void set(socket_t socket, short filter, ushort flags) @nogc
|
||||||
|
{
|
||||||
|
if (changes.length <= changeCount)
|
||||||
|
{
|
||||||
|
changes.length = changeCount + maxEvents;
|
||||||
|
}
|
||||||
|
EV_SET(&changes[changeCount],
|
||||||
|
cast(ulong) socket,
|
||||||
|
filter,
|
||||||
|
flags,
|
||||||
|
0U,
|
||||||
|
0L,
|
||||||
|
null);
|
||||||
|
++changeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be called if the backend configuration changes.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* watcher = Watcher.
|
||||||
|
* oldEvents = The events were already set.
|
||||||
|
* events = The events should be set.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
|
*/
|
||||||
|
override protected bool reify(SocketWatcher watcher,
|
||||||
|
EventMask oldEvents,
|
||||||
|
EventMask events) @nogc
|
||||||
|
{
|
||||||
|
if (events != oldEvents)
|
||||||
|
{
|
||||||
|
if (oldEvents & Event.read || oldEvents & Event.accept)
|
||||||
|
{
|
||||||
|
set(watcher.socket.handle, EVFILT_READ, EV_DELETE);
|
||||||
|
}
|
||||||
|
if (oldEvents & Event.write)
|
||||||
|
{
|
||||||
|
set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (events & (Event.read | events & Event.accept))
|
||||||
|
{
|
||||||
|
set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE);
|
||||||
|
}
|
||||||
|
if (events & Event.write)
|
||||||
|
{
|
||||||
|
set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the actual polling.
|
||||||
|
*/
|
||||||
|
protected override void poll() @nogc
|
||||||
|
{
|
||||||
|
timespec ts;
|
||||||
|
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
|
||||||
|
|
||||||
|
if (changeCount > maxEvents)
|
||||||
|
{
|
||||||
|
events.length = changes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto eventCount = kevent(fd,
|
||||||
|
changes.get().ptr,
|
||||||
|
cast(int) changeCount,
|
||||||
|
events.get().ptr,
|
||||||
|
maxEvents,
|
||||||
|
&ts);
|
||||||
|
changeCount = 0;
|
||||||
|
|
||||||
|
if (eventCount < 0)
|
||||||
|
{
|
||||||
|
if (errno != EINTR)
|
||||||
|
{
|
||||||
|
throw defaultAllocator.make!BadLoopException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i; i < eventCount; ++i)
|
||||||
|
{
|
||||||
|
assert(connections.length > events[i].ident);
|
||||||
|
|
||||||
|
auto transport = cast(StreamTransport) connections[events[i].ident];
|
||||||
|
// If it is a ConnectionWatcher. Accept connections.
|
||||||
|
if (transport is null)
|
||||||
|
{
|
||||||
|
auto connection = cast(ConnectionWatcher) connections[events[i].ident];
|
||||||
|
assert(connection !is null);
|
||||||
|
|
||||||
|
acceptConnections(connection);
|
||||||
|
}
|
||||||
|
else if (events[i].flags & EV_ERROR)
|
||||||
|
{
|
||||||
|
kill(transport);
|
||||||
|
}
|
||||||
|
else if (events[i].filter == EVFILT_READ)
|
||||||
|
{
|
||||||
|
SocketException exception;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ptrdiff_t received;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
received = transport.socket.receive(transport.output[]);
|
||||||
|
transport.output += received;
|
||||||
|
}
|
||||||
|
while (received);
|
||||||
|
}
|
||||||
|
catch (SocketException e)
|
||||||
|
{
|
||||||
|
exception = e;
|
||||||
|
}
|
||||||
|
if (transport.socket.disconnected)
|
||||||
|
{
|
||||||
|
kill(transport, exception);
|
||||||
|
}
|
||||||
|
else if (transport.output.length)
|
||||||
|
{
|
||||||
|
pendings.enqueue(transport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (events[i].filter == EVFILT_WRITE)
|
||||||
|
{
|
||||||
|
transport.writeReady = true;
|
||||||
|
if (transport.input.length)
|
||||||
|
{
|
||||||
|
feed(transport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: The blocking time.
|
||||||
|
*/
|
||||||
|
override protected @property inout(Duration) blockTime()
|
||||||
|
inout @nogc @safe pure nothrow
|
||||||
|
{
|
||||||
|
return min(super.blockTime, 1.dur!"seconds");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the transport couldn't send the data, the further sending should
|
||||||
|
* be handled by the event loop.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* transport = Transport.
|
||||||
|
* exception = Exception thrown on sending.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
||||||
|
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
||||||
|
* transport will be destroyed then).
|
||||||
|
*/
|
||||||
|
protected override bool feed(StreamTransport transport,
|
||||||
|
SocketException exception = null) @nogc
|
||||||
|
{
|
||||||
|
if (!super.feed(transport, exception))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!transport.writeReady)
|
||||||
|
{
|
||||||
|
set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
397
source/tanya/async/event/selector.d
Normal file
397
source/tanya/async/event/selector.d
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
*/
|
||||||
|
module tanya.async.event.selector;
|
||||||
|
|
||||||
|
version (Posix):
|
||||||
|
|
||||||
|
import tanya.async.loop;
|
||||||
|
import tanya.async.protocol;
|
||||||
|
import tanya.async.transport;
|
||||||
|
import tanya.async.watcher;
|
||||||
|
import tanya.container.buffer;
|
||||||
|
import tanya.container.vector;
|
||||||
|
import tanya.memory;
|
||||||
|
import tanya.memory.mmappool;
|
||||||
|
import tanya.network.socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transport for stream sockets.
|
||||||
|
*/
|
||||||
|
package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
||||||
|
{
|
||||||
|
private SelectorLoop loop;
|
||||||
|
|
||||||
|
private SocketException exception;
|
||||||
|
|
||||||
|
package ReadBuffer!ubyte output;
|
||||||
|
|
||||||
|
package WriteBuffer!ubyte input;
|
||||||
|
|
||||||
|
private Protocol protocol_;
|
||||||
|
|
||||||
|
private bool closing;
|
||||||
|
|
||||||
|
/// Received notification that the underlying socket is write-ready.
|
||||||
|
package bool writeReady;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* loop = Event loop.
|
||||||
|
* socket = Socket.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE loop !is null && socket !is null)
|
||||||
|
*/
|
||||||
|
this(SelectorLoop loop, ConnectedSocket socket) @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(loop !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
super(socket);
|
||||||
|
this.loop = loop;
|
||||||
|
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
|
||||||
|
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Socket.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE socket !is null)
|
||||||
|
*/
|
||||||
|
override @property ConnectedSocket socket() pure nothrow @safe @nogc
|
||||||
|
out (socket)
|
||||||
|
{
|
||||||
|
assert(socket !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return cast(ConnectedSocket) socket_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @property void socket(ConnectedSocket socket) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(socket !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
socket_ = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Application protocol.
|
||||||
|
*/
|
||||||
|
@property Protocol protocol() pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return protocol_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches the protocol.
|
||||||
|
*
|
||||||
|
* The protocol is deallocated by the event loop, it should currently be
|
||||||
|
* allocated with $(D_PSYMBOL MmapPool).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* protocol = Application protocol.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE protocol !is null)
|
||||||
|
*/
|
||||||
|
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(protocol !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
protocol_ = protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns $(D_PARAM true) if the transport is closing or closed.
|
||||||
|
*/
|
||||||
|
bool isClosing() const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return closing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the transport.
|
||||||
|
*
|
||||||
|
* Buffered data will be flushed. No more data will be received.
|
||||||
|
*/
|
||||||
|
void close() @nogc
|
||||||
|
{
|
||||||
|
closing = true;
|
||||||
|
loop.reify(this, EventMask(Event.read, Event.write), EventMask(Event.write));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the watcher callback.
|
||||||
|
*/
|
||||||
|
override void invoke() @nogc
|
||||||
|
{
|
||||||
|
if (output.length)
|
||||||
|
{
|
||||||
|
protocol.received(output[0 .. $]);
|
||||||
|
output.clear();
|
||||||
|
if (isClosing() && input.length == 0)
|
||||||
|
{
|
||||||
|
loop.kill(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
protocol.disconnected(exception);
|
||||||
|
MmapPool.instance.dispose(protocol_);
|
||||||
|
defaultAllocator.dispose(exception);
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write some data to the transport.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* data = Data to send.
|
||||||
|
*/
|
||||||
|
void write(ubyte[] data) @nogc
|
||||||
|
{
|
||||||
|
if (!data.length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Try to write if the socket is write ready.
|
||||||
|
if (writeReady)
|
||||||
|
{
|
||||||
|
ptrdiff_t sent;
|
||||||
|
SocketException exception;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sent = socket.send(data);
|
||||||
|
if (sent == 0)
|
||||||
|
{
|
||||||
|
writeReady = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException e)
|
||||||
|
{
|
||||||
|
writeReady = false;
|
||||||
|
exception = e;
|
||||||
|
}
|
||||||
|
if (sent < data.length)
|
||||||
|
{
|
||||||
|
input ~= data[sent..$];
|
||||||
|
loop.feed(this, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
input ~= data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class SelectorLoop : Loop
|
||||||
|
{
|
||||||
|
/// Pending connections.
|
||||||
|
protected Vector!SocketWatcher connections;
|
||||||
|
|
||||||
|
this() @nogc
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
connections = Vector!SocketWatcher(maxEvents, MmapPool.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
~this() @nogc
|
||||||
|
{
|
||||||
|
foreach (ref connection; connections)
|
||||||
|
{
|
||||||
|
// We want to free only the transports. ConnectionWatcher are created by the
|
||||||
|
// user and should be freed by himself.
|
||||||
|
if (cast(StreamTransport) connection !is null)
|
||||||
|
{
|
||||||
|
MmapPool.instance.dispose(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be called if the backend configuration changes.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* watcher = Watcher.
|
||||||
|
* oldEvents = The events were already set.
|
||||||
|
* events = The events should be set.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
|
*/
|
||||||
|
override abstract protected bool reify(SocketWatcher watcher,
|
||||||
|
EventMask oldEvents,
|
||||||
|
EventMask events) @nogc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kills the watcher and closes the connection.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* transport = Transport.
|
||||||
|
* exception = Occurred exception.
|
||||||
|
*/
|
||||||
|
protected void kill(StreamTransport transport,
|
||||||
|
SocketException exception = null) @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(transport !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
transport.socket.shutdown();
|
||||||
|
defaultAllocator.dispose(transport.socket);
|
||||||
|
transport.exception = exception;
|
||||||
|
pendings.enqueue(transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the transport couldn't send the data, the further sending should
|
||||||
|
* be handled by the event loop.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* transport = Transport.
|
||||||
|
* exception = Exception thrown on sending.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
||||||
|
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
||||||
|
* transport will be destroyed then).
|
||||||
|
*/
|
||||||
|
protected bool feed(StreamTransport transport,
|
||||||
|
SocketException exception = null) @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(transport !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
while (transport.input.length && transport.writeReady)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ptrdiff_t sent = transport.socket.send(transport.input[]);
|
||||||
|
if (sent == 0)
|
||||||
|
{
|
||||||
|
transport.writeReady = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transport.input += sent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException e)
|
||||||
|
{
|
||||||
|
exception = e;
|
||||||
|
transport.writeReady = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exception !is null)
|
||||||
|
{
|
||||||
|
kill(transport, exception);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (transport.input.length == 0 && transport.isClosing())
|
||||||
|
{
|
||||||
|
kill(transport);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start watching.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* watcher = Watcher.
|
||||||
|
*/
|
||||||
|
override void start(ConnectionWatcher watcher) @nogc
|
||||||
|
{
|
||||||
|
if (watcher.active)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connections.length <= watcher.socket)
|
||||||
|
{
|
||||||
|
connections.length = watcher.socket.handle + maxEvents / 2;
|
||||||
|
}
|
||||||
|
connections[watcher.socket.handle] = watcher;
|
||||||
|
|
||||||
|
super.start(watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept incoming connections.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* connection = Connection watcher ready to accept.
|
||||||
|
*/
|
||||||
|
package void acceptConnections(ConnectionWatcher connection) @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(connection !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ConnectedSocket client;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client = (cast(StreamSocket) connection.socket).accept();
|
||||||
|
}
|
||||||
|
catch (SocketException e)
|
||||||
|
{
|
||||||
|
defaultAllocator.dispose(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (client is null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamTransport transport;
|
||||||
|
|
||||||
|
if (connections.length > client.handle)
|
||||||
|
{
|
||||||
|
transport = cast(StreamTransport) connections[client.handle];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
connections.length = client.handle + maxEvents / 2;
|
||||||
|
}
|
||||||
|
if (transport is null)
|
||||||
|
{
|
||||||
|
transport = MmapPool.instance.make!StreamTransport(this, client);
|
||||||
|
connections[client.handle] = transport;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transport.socket = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||||
|
connection.incoming.enqueue(transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connection.incoming.empty)
|
||||||
|
{
|
||||||
|
pendings.enqueue(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
source/tanya/async/iocp.d
Normal file
32
source/tanya/async/iocp.d
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
*/
|
||||||
|
module tanya.async.iocp;
|
||||||
|
|
||||||
|
version (Windows):
|
||||||
|
|
||||||
|
import core.sys.windows.winbase;
|
||||||
|
import core.sys.windows.windef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an extendable representation of a Win32 $(D_PSYMBOL OVERLAPPED)
|
||||||
|
* structure.
|
||||||
|
*/
|
||||||
|
class State
|
||||||
|
{
|
||||||
|
/// For internal use by Windows API.
|
||||||
|
align(1) OVERLAPPED overlapped;
|
||||||
|
|
||||||
|
/// File/socket handle.
|
||||||
|
HANDLE handle;
|
||||||
|
|
||||||
|
/// For keeping events or event masks.
|
||||||
|
int event;
|
||||||
|
}
|
346
source/tanya/async/loop.d
Normal file
346
source/tanya/async/loop.d
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
*
|
||||||
|
* ---
|
||||||
|
* import tanya.async;
|
||||||
|
* import tanya.memory;
|
||||||
|
* import tanya.network.socket;
|
||||||
|
*
|
||||||
|
* class EchoProtocol : TransmissionControlProtocol
|
||||||
|
* {
|
||||||
|
* private DuplexTransport transport;
|
||||||
|
*
|
||||||
|
* void received(in ubyte[] data) @nogc
|
||||||
|
* {
|
||||||
|
* transport.write(data);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* void connected(DuplexTransport transport) @nogc
|
||||||
|
* {
|
||||||
|
* this.transport = transport;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* void disconnected(SocketException e) @nogc
|
||||||
|
* {
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* void main()
|
||||||
|
* {
|
||||||
|
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192);
|
||||||
|
*
|
||||||
|
* version (Windows)
|
||||||
|
* {
|
||||||
|
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET);
|
||||||
|
* }
|
||||||
|
* else
|
||||||
|
* {
|
||||||
|
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET);
|
||||||
|
* sock.blocking = false;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* sock.bind(address);
|
||||||
|
* sock.listen(5);
|
||||||
|
*
|
||||||
|
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
|
||||||
|
* io.setProtocol!EchoProtocol;
|
||||||
|
*
|
||||||
|
* defaultLoop.start(io);
|
||||||
|
* defaultLoop.run();
|
||||||
|
*
|
||||||
|
* sock.shutdown();
|
||||||
|
* defaultAllocator.dispose(io);
|
||||||
|
* defaultAllocator.dispose(sock);
|
||||||
|
* defaultAllocator.dispose(address);
|
||||||
|
* }
|
||||||
|
* ---
|
||||||
|
*/
|
||||||
|
module tanya.async.loop;
|
||||||
|
|
||||||
|
import core.time;
|
||||||
|
import std.algorithm.iteration;
|
||||||
|
import std.algorithm.mutation;
|
||||||
|
import std.typecons;
|
||||||
|
import tanya.async.transport;
|
||||||
|
import tanya.async.watcher;
|
||||||
|
import tanya.container.buffer;
|
||||||
|
import tanya.container.queue;
|
||||||
|
import tanya.memory;
|
||||||
|
import tanya.memory.mmappool;
|
||||||
|
import tanya.network.socket;
|
||||||
|
|
||||||
|
version (DisableBackends)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
else version (linux)
|
||||||
|
{
|
||||||
|
import tanya.async.event.epoll;
|
||||||
|
version = Epoll;
|
||||||
|
}
|
||||||
|
else version (Windows)
|
||||||
|
{
|
||||||
|
import tanya.async.event.iocp;
|
||||||
|
version = IOCP;
|
||||||
|
}
|
||||||
|
else version (OSX)
|
||||||
|
{
|
||||||
|
version = Kqueue;
|
||||||
|
}
|
||||||
|
else version (iOS)
|
||||||
|
{
|
||||||
|
version = Kqueue;
|
||||||
|
}
|
||||||
|
else version (FreeBSD)
|
||||||
|
{
|
||||||
|
version = Kqueue;
|
||||||
|
}
|
||||||
|
else version (OpenBSD)
|
||||||
|
{
|
||||||
|
version = Kqueue;
|
||||||
|
}
|
||||||
|
else version (DragonFlyBSD)
|
||||||
|
{
|
||||||
|
version = Kqueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Events.
|
||||||
|
*/
|
||||||
|
enum Event : uint
|
||||||
|
{
|
||||||
|
none = 0x00, /// No events.
|
||||||
|
read = 0x01, /// Non-blocking read call.
|
||||||
|
write = 0x02, /// Non-blocking write call.
|
||||||
|
accept = 0x04, /// Connection made.
|
||||||
|
error = 0x80000000, /// Sent when an error occurs.
|
||||||
|
}
|
||||||
|
|
||||||
|
alias EventMask = BitFlags!Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event loop.
|
||||||
|
*/
|
||||||
|
abstract class Loop
|
||||||
|
{
|
||||||
|
private bool done;
|
||||||
|
|
||||||
|
/// Pending watchers.
|
||||||
|
protected Queue!Watcher pendings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Maximal event count can be got at a time
|
||||||
|
* (should be supported by the backend).
|
||||||
|
*/
|
||||||
|
protected @property uint maxEvents()
|
||||||
|
const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return 128U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the loop.
|
||||||
|
*/
|
||||||
|
this() @nogc
|
||||||
|
{
|
||||||
|
pendings = Queue!Watcher(MmapPool.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees loop internals.
|
||||||
|
*/
|
||||||
|
~this() @nogc
|
||||||
|
{
|
||||||
|
foreach (w; pendings)
|
||||||
|
{
|
||||||
|
MmapPool.instance.dispose(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the loop.
|
||||||
|
*/
|
||||||
|
void run() @nogc
|
||||||
|
{
|
||||||
|
done = false;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
poll();
|
||||||
|
|
||||||
|
// Invoke pendings
|
||||||
|
foreach (ref w; pendings)
|
||||||
|
{
|
||||||
|
w.invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!done);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Break out of the loop.
|
||||||
|
*/
|
||||||
|
void unloop() @safe pure nothrow @nogc
|
||||||
|
{
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start watching.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* watcher = Watcher.
|
||||||
|
*/
|
||||||
|
void start(ConnectionWatcher watcher) @nogc
|
||||||
|
{
|
||||||
|
if (watcher.active)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
watcher.active = true;
|
||||||
|
|
||||||
|
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop watching.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* watcher = Watcher.
|
||||||
|
*/
|
||||||
|
void stop(ConnectionWatcher watcher) @nogc
|
||||||
|
{
|
||||||
|
if (!watcher.active)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
watcher.active = false;
|
||||||
|
|
||||||
|
reify(watcher, EventMask(Event.accept), EventMask(Event.none));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be called if the backend configuration changes.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* watcher = Watcher.
|
||||||
|
* oldEvents = The events were already set.
|
||||||
|
* events = The events should be set.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
|
*/
|
||||||
|
abstract protected bool reify(SocketWatcher watcher,
|
||||||
|
EventMask oldEvents,
|
||||||
|
EventMask events) @nogc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: The blocking time.
|
||||||
|
*/
|
||||||
|
protected @property inout(Duration) blockTime()
|
||||||
|
inout @safe pure nothrow @nogc
|
||||||
|
{
|
||||||
|
// Don't block if we have to do.
|
||||||
|
return pendings.empty ? blockTime_ : Duration.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the blocking time for IO watchers.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* blockTime = The blocking time. Cannot be larger than
|
||||||
|
* $(D_PSYMBOL maxBlockTime).
|
||||||
|
*/
|
||||||
|
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
|
||||||
|
assert(!blockTime.isNegative);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
blockTime_ = blockTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the actual polling.
|
||||||
|
*/
|
||||||
|
abstract protected void poll() @nogc;
|
||||||
|
|
||||||
|
/// Maximal block time.
|
||||||
|
protected Duration blockTime_ = 1.dur!"minutes";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown on errors in the event loop.
|
||||||
|
*/
|
||||||
|
class BadLoopException : Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* file = The file where the exception occurred.
|
||||||
|
* line = The line number where the exception occurred.
|
||||||
|
* next = The previous exception in the chain of exceptions, if any.
|
||||||
|
*/
|
||||||
|
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
|
||||||
|
pure nothrow const @safe @nogc
|
||||||
|
{
|
||||||
|
super("Event loop cannot be initialized.", file, line, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the event loop used by default. If an event loop wasn't set with
|
||||||
|
* $(D_PSYMBOL defaultLoop) before, $(D_PSYMBOL defaultLoop) will try to
|
||||||
|
* choose an event loop supported on the system.
|
||||||
|
*
|
||||||
|
* Returns: The default event loop.
|
||||||
|
*/
|
||||||
|
@property Loop defaultLoop() @nogc
|
||||||
|
{
|
||||||
|
if (defaultLoop_ !is null)
|
||||||
|
{
|
||||||
|
return defaultLoop_;
|
||||||
|
}
|
||||||
|
version (Epoll)
|
||||||
|
{
|
||||||
|
defaultLoop_ = MmapPool.instance.make!EpollLoop;
|
||||||
|
}
|
||||||
|
else version (IOCP)
|
||||||
|
{
|
||||||
|
defaultLoop_ = MmapPool.instance.make!IOCPLoop;
|
||||||
|
}
|
||||||
|
else version (Kqueue)
|
||||||
|
{
|
||||||
|
import tanya.async.event.kqueue;
|
||||||
|
defaultLoop_ = MmapPool.instance.make!KqueueLoop;
|
||||||
|
}
|
||||||
|
return defaultLoop_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default event loop.
|
||||||
|
*
|
||||||
|
* This property makes it possible to implement your own backends or event
|
||||||
|
* loops, for example, if the system is not supported or if you want to
|
||||||
|
* extend the supported implementation. Just extend $(D_PSYMBOL Loop) and pass
|
||||||
|
* your implementation to this property.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* loop = The event loop.
|
||||||
|
*/
|
||||||
|
@property void defaultLoop(Loop loop) @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(loop !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
defaultLoop_ = loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Loop defaultLoop_;
|
@ -1,16 +1,16 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.event;
|
module tanya.async;
|
||||||
|
|
||||||
public import tanya.event.loop;
|
public import tanya.async.loop;
|
||||||
public import tanya.event.protocol;
|
public import tanya.async.protocol;
|
||||||
public import tanya.event.transport;
|
public import tanya.async.transport;
|
||||||
public import tanya.event.watcher;
|
public import tanya.async.watcher;
|
@ -1,46 +1,50 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.event.protocol;
|
module tanya.async.protocol;
|
||||||
|
|
||||||
import tanya.event.transport;
|
import tanya.network.socket;
|
||||||
|
import tanya.async.transport;
|
||||||
/**
|
|
||||||
* Common protocol interface.
|
/**
|
||||||
*/
|
* Common protocol interface.
|
||||||
interface Protocol
|
*/
|
||||||
{
|
interface Protocol
|
||||||
@nogc:
|
{
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* data = Read data.
|
* data = Read data.
|
||||||
*/
|
*/
|
||||||
void received(ubyte[] data);
|
void received(in ubyte[] data) @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a connection is made.
|
* Called when a connection is made.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* transport = Protocol transport.
|
* transport = Protocol transport.
|
||||||
*/
|
*/
|
||||||
void connected(DuplexTransport transport);
|
void connected(DuplexTransport transport) @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a connection is lost.
|
* Called when a connection is lost.
|
||||||
*/
|
*
|
||||||
void disconnected();
|
* Params:
|
||||||
}
|
* exception = $(D_PSYMBOL Exception) if an error caused
|
||||||
|
* the disconnect, $(D_KEYWORD null) otherwise.
|
||||||
/**
|
*/
|
||||||
* Interface for TCP.
|
void disconnected(SocketException exception) @nogc;
|
||||||
*/
|
}
|
||||||
interface TransmissionControlProtocol : Protocol
|
|
||||||
{
|
/**
|
||||||
}
|
* Interface for TCP.
|
||||||
|
*/
|
||||||
|
interface TransmissionControlProtocol : Protocol
|
||||||
|
{
|
||||||
|
}
|
100
source/tanya/async/transport.d
Normal file
100
source/tanya/async/transport.d
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
*/
|
||||||
|
module tanya.async.transport;
|
||||||
|
|
||||||
|
import tanya.async.protocol;
|
||||||
|
import tanya.network.socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base transport interface.
|
||||||
|
*/
|
||||||
|
interface Transport
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for read-only transports.
|
||||||
|
*/
|
||||||
|
interface ReadTransport : Transport
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for write-only transports.
|
||||||
|
*/
|
||||||
|
interface WriteTransport : Transport
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Write some data to the transport.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* data = Data to send.
|
||||||
|
*/
|
||||||
|
void write(ubyte[] data) @nogc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a bidirectional transport.
|
||||||
|
*/
|
||||||
|
interface DuplexTransport : ReadTransport, WriteTransport
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns: Application protocol.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE protocol !is null)
|
||||||
|
*/
|
||||||
|
@property Protocol protocol() pure nothrow @safe @nogc
|
||||||
|
out (protocol)
|
||||||
|
{
|
||||||
|
assert(protocol !is null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches the protocol.
|
||||||
|
*
|
||||||
|
* The protocol is deallocated by the event loop, it should currently be
|
||||||
|
* allocated with $(D_PSYMBOL MmapPool).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* protocol = Application protocol.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE protocol !is null)
|
||||||
|
*/
|
||||||
|
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(protocol !is null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns $(D_PARAM true) if the transport is closing or closed.
|
||||||
|
*/
|
||||||
|
bool isClosing() const pure nothrow @safe @nogc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the transport.
|
||||||
|
*
|
||||||
|
* Buffered data will be flushed. No more data will be received.
|
||||||
|
*/
|
||||||
|
void close() @nogc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a socket transport.
|
||||||
|
*/
|
||||||
|
interface SocketTransport : Transport
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns: Socket.
|
||||||
|
*/
|
||||||
|
@property Socket socket() pure nothrow @safe @nogc;
|
||||||
|
}
|
117
source/tanya/async/watcher.d
Normal file
117
source/tanya/async/watcher.d
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
*/
|
||||||
|
module tanya.async.watcher;
|
||||||
|
|
||||||
|
import std.functional;
|
||||||
|
import std.exception;
|
||||||
|
import tanya.async.loop;
|
||||||
|
import tanya.async.protocol;
|
||||||
|
import tanya.async.transport;
|
||||||
|
import tanya.container.buffer;
|
||||||
|
import tanya.container.queue;
|
||||||
|
import tanya.memory;
|
||||||
|
import tanya.memory.mmappool;
|
||||||
|
import tanya.network.socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A watcher is an opaque structure that you allocate and register to record
|
||||||
|
* your interest in some event.
|
||||||
|
*/
|
||||||
|
abstract class Watcher
|
||||||
|
{
|
||||||
|
/// Whether the watcher is active.
|
||||||
|
bool active;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke some action on event.
|
||||||
|
*/
|
||||||
|
void invoke() @nogc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket watcher.
|
||||||
|
*/
|
||||||
|
abstract class SocketWatcher : Watcher
|
||||||
|
{
|
||||||
|
/// Watched socket.
|
||||||
|
protected Socket socket_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* socket = Socket.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE socket !is null)
|
||||||
|
*/
|
||||||
|
this(Socket socket) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(socket !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
socket_ = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Socket.
|
||||||
|
*/
|
||||||
|
@property Socket socket() pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return socket_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connection watcher.
|
||||||
|
*/
|
||||||
|
class ConnectionWatcher : SocketWatcher
|
||||||
|
{
|
||||||
|
/// Incoming connection queue.
|
||||||
|
Queue!DuplexTransport incoming;
|
||||||
|
|
||||||
|
private Protocol delegate() @nogc protocolFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* socket = Socket.
|
||||||
|
*/
|
||||||
|
this(Socket socket) @nogc
|
||||||
|
{
|
||||||
|
super(socket);
|
||||||
|
incoming = Queue!DuplexTransport(MmapPool.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* P = Protocol should be used.
|
||||||
|
*/
|
||||||
|
void setProtocol(P : Protocol)() @nogc
|
||||||
|
{
|
||||||
|
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes new connection callback.
|
||||||
|
*/
|
||||||
|
override void invoke() @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(protocolFactory !is null, "Protocol isn't set.");
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
foreach (transport; incoming)
|
||||||
|
{
|
||||||
|
transport.protocol = protocolFactory();
|
||||||
|
transport.protocol.connected(transport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -2,25 +2,21 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Internal package used by containers that rely on entries/nodes.
|
||||||
|
*
|
||||||
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.event.config;
|
module tanya.container.entry;
|
||||||
|
|
||||||
package version (DisableBackends)
|
package struct SEntry(T)
|
||||||
{
|
{
|
||||||
}
|
/// Item content.
|
||||||
else
|
T content;
|
||||||
{
|
|
||||||
version (linux)
|
/// Next item.
|
||||||
{
|
SEntry* next;
|
||||||
enum UseEpoll = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
enum UseEpoll = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -3,63 +3,131 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.container.list;
|
module tanya.container.list;
|
||||||
|
|
||||||
|
import std.algorithm.comparison;
|
||||||
|
import std.algorithm.searching;
|
||||||
|
import std.traits;
|
||||||
|
import tanya.container.entry;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
|
|
||||||
|
private struct Range(E)
|
||||||
|
if (__traits(isSame, TemplateOf!E, SEntry))
|
||||||
|
{
|
||||||
|
private alias T = typeof(E.content);
|
||||||
|
|
||||||
|
private E* head;
|
||||||
|
|
||||||
|
private this(E* head)
|
||||||
|
{
|
||||||
|
this.head = head;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property Range save()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property size_t length() const
|
||||||
|
{
|
||||||
|
return count(opIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
@property bool empty() const
|
||||||
|
{
|
||||||
|
return head is null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property ref inout(T) front() inout
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(!empty);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return head.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
void popFront()
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(!empty);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
head = head.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
Range opIndex()
|
||||||
|
{
|
||||||
|
return typeof(return)(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
Range!(const E) opIndex() const
|
||||||
|
{
|
||||||
|
return typeof(return)(head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singly linked list.
|
* Singly-linked list.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* T = Content type.
|
* T = Content type.
|
||||||
*/
|
*/
|
||||||
class SList(T)
|
struct SList(T)
|
||||||
{
|
{
|
||||||
@nogc:
|
private alias Entry = SEntry!T;
|
||||||
/**
|
|
||||||
* Creates a new $(D_PSYMBOL SList).
|
// 0th element of the list.
|
||||||
*
|
private Entry* head;
|
||||||
* Params:
|
|
||||||
* allocator = The allocator should be used for the element
|
|
||||||
* allocations.
|
|
||||||
*/
|
|
||||||
this(Allocator allocator = defaultAllocator)
|
|
||||||
{
|
|
||||||
this.allocator = allocator;
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all elements from the list.
|
* Removes all elements from the list.
|
||||||
*/
|
*/
|
||||||
~this()
|
~this()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all contents from the list.
|
||||||
|
*/
|
||||||
|
void clear()
|
||||||
{
|
{
|
||||||
while (!empty)
|
while (!empty)
|
||||||
{
|
{
|
||||||
static if (isFinalizable!T)
|
removeFront();
|
||||||
{
|
|
||||||
finalize(allocator, front);
|
|
||||||
}
|
|
||||||
popFront();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
SList!int l;
|
||||||
|
|
||||||
|
l.insertFront(8);
|
||||||
|
l.insertFront(5);
|
||||||
|
l.clear();
|
||||||
|
assert(l.empty);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: First element.
|
* Returns: First element.
|
||||||
*/
|
*/
|
||||||
@property ref T front()
|
@property ref inout(T) front() inout
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(!empty);
|
assert(!empty);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
return first.next.content;
|
return head.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,171 +136,185 @@ class SList(T)
|
|||||||
* Params:
|
* Params:
|
||||||
* x = New element.
|
* x = New element.
|
||||||
*/
|
*/
|
||||||
@property void front(T x)
|
void insertFront(ref T x)
|
||||||
{
|
{
|
||||||
Entry* temp = make!Entry(allocator);
|
auto temp = allocator.make!Entry;
|
||||||
|
|
||||||
temp.content = x;
|
temp.content = x;
|
||||||
temp.next = first.next;
|
temp.next = head;
|
||||||
first.next = temp;
|
head = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
void insertFront(T x)
|
||||||
|
{
|
||||||
|
insertFront(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
alias insert = insertFront;
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!int)(defaultAllocator);
|
SList!int l;
|
||||||
int[2] values = [8, 9];
|
|
||||||
|
|
||||||
l.front = values[0];
|
l.insertFront(8);
|
||||||
assert(l.front == values[0]);
|
assert(l.front == 8);
|
||||||
l.front = values[1];
|
l.insertFront(9);
|
||||||
assert(l.front == values[1]);
|
assert(l.front == 9);
|
||||||
|
|
||||||
finalize(defaultAllocator, l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a new element at the beginning.
|
* Returns: How many elements are in the list.
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* x = New element.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD this).
|
|
||||||
*/
|
*/
|
||||||
typeof(this) opOpAssign(string Op)(ref T x)
|
@property size_t length() const
|
||||||
if (Op == "~")
|
|
||||||
{
|
{
|
||||||
front = x;
|
return count(opIndex());
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!int)(defaultAllocator);
|
SList!int l;
|
||||||
int value = 5;
|
|
||||||
|
|
||||||
assert(l.empty);
|
l.insertFront(8);
|
||||||
|
l.insertFront(9);
|
||||||
|
assert(l.length == 2);
|
||||||
|
l.removeFront();
|
||||||
|
assert(l.length == 1);
|
||||||
|
l.removeFront();
|
||||||
|
assert(l.length == 0);
|
||||||
|
}
|
||||||
|
|
||||||
l ~= value;
|
/**
|
||||||
|
* Comparison for equality.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* that = The list to compare with.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the lists are equal, $(D_KEYWORD false)
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
bool opEquals()(auto ref typeof(this) that) @trusted
|
||||||
|
{
|
||||||
|
return equal(opIndex(), that[]);
|
||||||
|
}
|
||||||
|
|
||||||
assert(l.front == value);
|
/// Ditto.
|
||||||
assert(!l.empty);
|
bool opEquals()(in auto ref typeof(this) that) const @trusted
|
||||||
|
{
|
||||||
|
return equal(opIndex(), that[]);
|
||||||
|
}
|
||||||
|
|
||||||
finalize(defaultAllocator, l);
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
SList!int l1, l2;
|
||||||
|
|
||||||
|
l1.insertFront(8);
|
||||||
|
l1.insertFront(9);
|
||||||
|
l2.insertFront(8);
|
||||||
|
l2.insertFront(10);
|
||||||
|
assert(l1 != l2);
|
||||||
|
|
||||||
|
l1.removeFront();
|
||||||
|
assert(l1 != l2);
|
||||||
|
|
||||||
|
l2.removeFront();
|
||||||
|
assert(l1 == l2);
|
||||||
|
|
||||||
|
l1.removeFront();
|
||||||
|
assert(l1 != l2);
|
||||||
|
|
||||||
|
l2.removeFront();
|
||||||
|
assert(l1 == l2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: $(D_KEYWORD true) if the list is empty.
|
* Returns: $(D_KEYWORD true) if the list is empty.
|
||||||
*/
|
*/
|
||||||
@property bool empty() const @safe pure nothrow
|
@property bool empty() const
|
||||||
{
|
{
|
||||||
return first.next is null;
|
return head is null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first element and moves to the next one.
|
* Returns the first element and moves to the next one.
|
||||||
*
|
*
|
||||||
* Returns: The first element.
|
* Returns: The first element.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE !empty)
|
||||||
*/
|
*/
|
||||||
T popFront()
|
void removeFront()
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(!empty);
|
assert(!empty);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
auto n = first.next.next;
|
auto n = head.next;
|
||||||
auto content = first.next.content;
|
|
||||||
|
|
||||||
finalize(allocator, first.next);
|
allocator.dispose(head);
|
||||||
first.next = n;
|
head = n;
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!int)(defaultAllocator);
|
SList!int l;
|
||||||
int[2] values = [8, 9];
|
|
||||||
|
|
||||||
l.front = values[0];
|
l.insertFront(8);
|
||||||
l.front = values[1];
|
l.insertFront(9);
|
||||||
assert(l.front == values[1]);
|
assert(l.front == 9);
|
||||||
l.popFront();
|
l.removeFront();
|
||||||
assert(l.front == values[0]);
|
assert(l.front == 8);
|
||||||
|
l.removeFront();
|
||||||
finalize(defaultAllocator, l);
|
assert(l.empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current item from the list and removes from the list.
|
* Removes $(D_PARAM howMany) elements from the list.
|
||||||
|
*
|
||||||
|
* Unlike $(D_PSYMBOL removeFront()), this method doesn't fail, if it could not
|
||||||
|
* remove $(D_PARAM howMany) elements. Instead, if $(D_PARAM howMany) is
|
||||||
|
* greater than the list length, all elements are removed.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* x = The item should be removed.
|
* howMany = How many elements should be removed.
|
||||||
*
|
*
|
||||||
* Returns: Removed item.
|
* Returns: The number of elements removed.
|
||||||
*/
|
*/
|
||||||
T remove()
|
size_t removeFront(in size_t howMany)
|
||||||
in
|
out (removed)
|
||||||
{
|
{
|
||||||
assert(!empty);
|
assert(removed <= howMany);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
auto temp = position.next.next;
|
size_t i;
|
||||||
auto content = position.next.content;
|
for (; i < howMany && !empty; ++i)
|
||||||
|
{
|
||||||
finalize(allocator, position.next);
|
removeFront();
|
||||||
position.next = temp;
|
}
|
||||||
|
return i;
|
||||||
return content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
alias remove = removeFront;
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!int)(defaultAllocator);
|
SList!int l;
|
||||||
int[3] values = [8, 5, 4];
|
|
||||||
|
|
||||||
l.front = values[0];
|
l.insertFront(8);
|
||||||
l.front = values[1];
|
l.insertFront(5);
|
||||||
assert(l.remove() == 5);
|
l.insertFront(4);
|
||||||
l.front = values[2];
|
assert(l.removeFront(0) == 0);
|
||||||
assert(l.remove() == 4);
|
assert(l.removeFront(2) == 2);
|
||||||
assert(l.remove() == 8);
|
assert(l.removeFront(3) == 1);
|
||||||
assert(l.empty);
|
assert(l.removeFront(3) == 0);
|
||||||
|
|
||||||
finalize(defaultAllocator, l);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the current position.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD this).
|
|
||||||
*/
|
|
||||||
typeof(this) reset()
|
|
||||||
{
|
|
||||||
position = &first;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
auto l = make!(SList!int)(defaultAllocator);
|
|
||||||
int[2] values = [8, 5];
|
|
||||||
|
|
||||||
l.current = values[0];
|
|
||||||
l.current = values[1];
|
|
||||||
assert(l.current == 5);
|
|
||||||
l.advance();
|
|
||||||
assert(l.current == 8);
|
|
||||||
l.reset();
|
|
||||||
assert(l.current == 5);
|
|
||||||
|
|
||||||
finalize(defaultAllocator, l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -240,166 +322,95 @@ class SList(T)
|
|||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* dg = $(D_KEYWORD foreach) body.
|
* dg = $(D_KEYWORD foreach) body.
|
||||||
|
*
|
||||||
|
* Returns: The value returned from $(D_PARAM dg).
|
||||||
*/
|
*/
|
||||||
int opApply(int delegate(ref size_t i, ref T) @nogc dg)
|
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
for (position = first.next; position; position = position.next, ++i)
|
for (auto pos = head; pos; pos = pos.next, ++i)
|
||||||
{
|
{
|
||||||
result = dg(i, position.content);
|
result = dg(i, pos.content);
|
||||||
|
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reset();
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
auto l = make!(SList!int)(defaultAllocator);
|
|
||||||
int[3] values = [5, 4, 9];
|
|
||||||
|
|
||||||
l.front = values[0];
|
|
||||||
l.front = values[1];
|
|
||||||
l.front = values[2];
|
|
||||||
foreach (i, e; l)
|
|
||||||
{
|
|
||||||
assert(i != 0 || e == values[2]);
|
|
||||||
assert(i != 1 || e == values[1]);
|
|
||||||
assert(i != 2 || e == values[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize(defaultAllocator, l);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
int opApply(int delegate(ref T) @nogc dg)
|
int opApply(scope int delegate(ref T) @nogc dg)
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
|
|
||||||
for (position = first.next; position; position = position.next)
|
for (auto pos = head; pos; pos = pos.next)
|
||||||
{
|
{
|
||||||
result = dg(position.content);
|
result = dg(pos.content);
|
||||||
|
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reset();
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!int)(defaultAllocator);
|
SList!int l;
|
||||||
int[3] values = [5, 4, 9];
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
l.front = values[0];
|
l.insertFront(5);
|
||||||
l.front = values[1];
|
l.insertFront(4);
|
||||||
l.front = values[2];
|
l.insertFront(9);
|
||||||
foreach (e; l)
|
foreach (i, e; l)
|
||||||
{
|
{
|
||||||
assert(i != 0 || e == values[2]);
|
assert(i != 0 || e == 9);
|
||||||
assert(i != 1 || e == values[1]);
|
assert(i != 1 || e == 4);
|
||||||
assert(i != 2 || e == values[0]);
|
assert(i != 2 || e == 5);
|
||||||
++i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finalize(defaultAllocator, l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
Range!Entry opIndex()
|
||||||
* Returns: $(D_KEYWORD true) if the current position is the end position.
|
|
||||||
*/
|
|
||||||
@property bool end() const
|
|
||||||
{
|
{
|
||||||
return empty || position.next.next is null;
|
return typeof(return)(head);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
Range!(const Entry) opIndex() const
|
||||||
* Moves to the next element and returns it.
|
|
||||||
*
|
|
||||||
* Returns: The element on the next position.
|
|
||||||
*/
|
|
||||||
T advance()
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
assert(!end);
|
return typeof(return)(head);
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
position = position.next;
|
|
||||||
return position.content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
mixin DefaultAllocator;
|
||||||
* Returns: Element on the current position.
|
|
||||||
*/
|
|
||||||
@property ref T current()
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(!empty);
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
return position.next.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a new element at the current position.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* x = New element.
|
|
||||||
*/
|
|
||||||
@property void current(T x)
|
|
||||||
{
|
|
||||||
Entry* temp = make!Entry(allocator);
|
|
||||||
|
|
||||||
temp.content = x;
|
|
||||||
temp.next = position.next;
|
|
||||||
position.next = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List entry.
|
|
||||||
*/
|
|
||||||
protected struct Entry
|
|
||||||
{
|
|
||||||
/// List item content.
|
|
||||||
T content;
|
|
||||||
|
|
||||||
/// Next list item.
|
|
||||||
Entry* next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 0th element of the list.
|
|
||||||
protected Entry first;
|
|
||||||
|
|
||||||
/// Current position in the list.
|
|
||||||
protected Entry* position;
|
|
||||||
|
|
||||||
private Allocator allocator;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Stuff
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!Stuff)(defaultAllocator);
|
SList!int l;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
finalize(defaultAllocator, l);
|
l.insertFront(5);
|
||||||
|
l.insertFront(4);
|
||||||
|
l.insertFront(9);
|
||||||
|
foreach (e; l)
|
||||||
|
{
|
||||||
|
assert(i != 0 || e == 9);
|
||||||
|
assert(i != 1 || e == 4);
|
||||||
|
assert(i != 2 || e == 5);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
assert(i == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
interface Stuff
|
||||||
|
{
|
||||||
|
}
|
||||||
|
static assert(is(SList!Stuff));
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
@ -3,104 +3,96 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.container.queue;
|
module tanya.container.queue;
|
||||||
|
|
||||||
|
import core.exception;
|
||||||
|
import std.traits;
|
||||||
|
import std.algorithm.mutation;
|
||||||
|
import tanya.container.entry;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue.
|
* FIFO queue.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* T = Content type.
|
* T = Content type.
|
||||||
*/
|
*/
|
||||||
class Queue(T)
|
struct Queue(T)
|
||||||
{
|
{
|
||||||
@nogc:
|
|
||||||
/**
|
|
||||||
* Creates a new $(D_PSYMBOL Queue).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* allocator = The allocator should be used for the element
|
|
||||||
* allocations.
|
|
||||||
*/
|
|
||||||
this(Allocator allocator = defaultAllocator)
|
|
||||||
{
|
|
||||||
this.allocator = allocator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all elements from the queue.
|
* Removes all elements from the queue.
|
||||||
*/
|
*/
|
||||||
~this()
|
~this()
|
||||||
{
|
{
|
||||||
foreach (e; this)
|
while (!empty)
|
||||||
{
|
{
|
||||||
static if (isFinalizable!T)
|
dequeue();
|
||||||
{
|
|
||||||
finalize(allocator, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: First element.
|
* Returns how many elements are in the queue. It iterates through the queue
|
||||||
|
* to count the elements.
|
||||||
|
*
|
||||||
|
* Returns: How many elements are in the queue.
|
||||||
*/
|
*/
|
||||||
@property ref T front()
|
size_t length() const
|
||||||
in
|
|
||||||
{
|
{
|
||||||
assert(!empty);
|
size_t len;
|
||||||
}
|
for (const(SEntry!T)* i = first; i !is null; i = i.next)
|
||||||
body
|
{
|
||||||
{
|
++len;
|
||||||
return first.next.content;
|
}
|
||||||
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
///
|
||||||
* Inserts a new element.
|
unittest
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* x = New element.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD this).
|
|
||||||
*/
|
|
||||||
typeof(this) insertBack(T x)
|
|
||||||
{
|
{
|
||||||
Entry* temp = make!Entry(allocator);
|
Queue!int q;
|
||||||
|
|
||||||
temp.content = x;
|
|
||||||
|
|
||||||
|
assert(q.length == 0);
|
||||||
|
q.enqueue(5);
|
||||||
|
assert(q.length == 1);
|
||||||
|
q.enqueue(4);
|
||||||
|
assert(q.length == 2);
|
||||||
|
q.enqueue(9);
|
||||||
|
assert(q.length == 3);
|
||||||
|
|
||||||
|
q.dequeue();
|
||||||
|
assert(q.length == 2);
|
||||||
|
q.dequeue();
|
||||||
|
assert(q.length == 1);
|
||||||
|
q.dequeue();
|
||||||
|
assert(q.length == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enqueueEntry(ref SEntry!T* entry)
|
||||||
|
{
|
||||||
if (empty)
|
if (empty)
|
||||||
{
|
{
|
||||||
first.next = rear = temp;
|
first = rear = entry;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
rear.next = temp;
|
rear.next = entry;
|
||||||
rear = rear.next;
|
rear = rear.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
alias insert = insertBack;
|
private SEntry!T* allocateEntry()
|
||||||
|
|
||||||
///
|
|
||||||
unittest
|
|
||||||
{
|
{
|
||||||
auto q = make!(Queue!int)(defaultAllocator);
|
auto temp = cast(SEntry!T*) allocator.allocate(SEntry!T.sizeof);
|
||||||
int[2] values = [8, 9];
|
if (temp is null)
|
||||||
|
{
|
||||||
q.insertBack(values[0]);
|
onOutOfMemoryError();
|
||||||
assert(q.front is values[0]);
|
}
|
||||||
q.insertBack(values[1]);
|
return temp;
|
||||||
assert(q.front is values[0]);
|
|
||||||
|
|
||||||
finalize(defaultAllocator, q);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,112 +100,187 @@ class Queue(T)
|
|||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* x = New element.
|
* x = New element.
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD this).
|
|
||||||
*/
|
*/
|
||||||
typeof(this) opOpAssign(string Op)(ref T x)
|
void enqueue(ref T x)
|
||||||
if (Op == "~")
|
|
||||||
{
|
{
|
||||||
return insertBack(x);
|
auto temp = allocateEntry();
|
||||||
|
|
||||||
|
*temp = SEntry!T.init;
|
||||||
|
temp.content = x;
|
||||||
|
|
||||||
|
enqueueEntry(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
void enqueue(T x)
|
||||||
|
{
|
||||||
|
auto temp = allocateEntry();
|
||||||
|
|
||||||
|
moveEmplace(x, (*temp).content);
|
||||||
|
(*temp).next = null;
|
||||||
|
|
||||||
|
enqueueEntry(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto q = make!(Queue!int)(defaultAllocator);
|
Queue!int q;
|
||||||
int value = 5;
|
|
||||||
|
|
||||||
assert(q.empty);
|
assert(q.empty);
|
||||||
|
q.enqueue(8);
|
||||||
q ~= value;
|
q.enqueue(9);
|
||||||
|
assert(q.dequeue() == 8);
|
||||||
assert(q.front == value);
|
assert(q.dequeue() == 9);
|
||||||
assert(!q.empty);
|
|
||||||
|
|
||||||
finalize(defaultAllocator, q);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: $(D_KEYWORD true) if the queue is empty.
|
* Returns: $(D_KEYWORD true) if the queue is empty.
|
||||||
*/
|
*/
|
||||||
@property bool empty() const @safe pure nothrow
|
@property bool empty() const
|
||||||
{
|
{
|
||||||
return first.next is null;
|
return first is null;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto q = make!(Queue!int)(defaultAllocator);
|
Queue!int q;
|
||||||
int value = 7;
|
int value = 7;
|
||||||
|
|
||||||
assert(q.empty);
|
assert(q.empty);
|
||||||
q.insertBack(value);
|
q.enqueue(value);
|
||||||
assert(!q.empty);
|
assert(!q.empty);
|
||||||
|
|
||||||
finalize(defaultAllocator, q);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move position to the next element.
|
* Move the position to the next element.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD this).
|
* Returns: Dequeued element.
|
||||||
*/
|
*/
|
||||||
typeof(this) popFront()
|
T dequeue()
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(!empty);
|
assert(!empty);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
auto n = first.next.next;
|
auto n = first.next;
|
||||||
|
T ret = move(first.content);
|
||||||
|
|
||||||
finalize(allocator, first.next);
|
allocator.dispose(first);
|
||||||
first.next = n;
|
first = n;
|
||||||
|
return ret;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto q = make!(Queue!int)(defaultAllocator);
|
Queue!int q;
|
||||||
int[2] values = [8, 9];
|
|
||||||
|
|
||||||
q.insertBack(values[0]);
|
q.enqueue(8);
|
||||||
q.insertBack(values[1]);
|
q.enqueue(9);
|
||||||
assert(q.front is values[0]);
|
assert(q.dequeue() == 8);
|
||||||
q.popFront();
|
assert(q.dequeue() == 9);
|
||||||
assert(q.front is values[1]);
|
|
||||||
|
|
||||||
finalize(defaultAllocator, q);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue entry.
|
* $(D_KEYWORD foreach) iteration. The elements will be automatically
|
||||||
|
* dequeued.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* dg = $(D_KEYWORD foreach) body.
|
||||||
|
*
|
||||||
|
* Returns: The value returned from $(D_PARAM dg).
|
||||||
*/
|
*/
|
||||||
protected struct Entry
|
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
|
||||||
{
|
{
|
||||||
/// Queue item content.
|
int result;
|
||||||
T content;
|
|
||||||
|
|
||||||
/// Next list item.
|
for (size_t i = 0; !empty; ++i)
|
||||||
Entry* next;
|
{
|
||||||
|
auto e = dequeue();
|
||||||
|
if ((result = dg(i, e)) != 0)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The first element of the list.
|
/// Ditto.
|
||||||
protected Entry first;
|
int opApply(scope int delegate(ref T) @nogc dg)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
/// The last element of the list.
|
while (!empty)
|
||||||
protected Entry* rear;
|
{
|
||||||
|
auto e = dequeue();
|
||||||
|
if ((result = dg(e)) != 0)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private Allocator allocator;
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
Queue!int q;
|
||||||
|
|
||||||
|
size_t j;
|
||||||
|
q.enqueue(5);
|
||||||
|
q.enqueue(4);
|
||||||
|
q.enqueue(9);
|
||||||
|
foreach (i, e; q)
|
||||||
|
{
|
||||||
|
assert(i != 2 || e == 9);
|
||||||
|
assert(i != 1 || e == 4);
|
||||||
|
assert(i != 0 || e == 5);
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
assert(j == 3);
|
||||||
|
assert(q.empty);
|
||||||
|
|
||||||
|
j = 0;
|
||||||
|
q.enqueue(5);
|
||||||
|
q.enqueue(4);
|
||||||
|
q.enqueue(9);
|
||||||
|
foreach (e; q)
|
||||||
|
{
|
||||||
|
assert(j != 2 || e == 9);
|
||||||
|
assert(j != 1 || e == 4);
|
||||||
|
assert(j != 0 || e == 5);
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
assert(j == 3);
|
||||||
|
assert(q.empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SEntry!T* first;
|
||||||
|
private SEntry!T* rear;
|
||||||
|
|
||||||
|
mixin DefaultAllocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto q = make!(Queue!int)(defaultAllocator);
|
Queue!int q;
|
||||||
|
|
||||||
finalize(defaultAllocator, q);
|
q.enqueue(5);
|
||||||
|
assert(!q.empty);
|
||||||
|
|
||||||
|
q.enqueue(4);
|
||||||
|
q.enqueue(9);
|
||||||
|
|
||||||
|
assert(q.dequeue() == 5);
|
||||||
|
|
||||||
|
foreach (i, ref e; q)
|
||||||
|
{
|
||||||
|
assert(i != 0 || e == 4);
|
||||||
|
assert(i != 1 || e == 9);
|
||||||
|
}
|
||||||
|
assert(q.empty);
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,191 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright: Eugene Wissner 2016.
|
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
||||||
* Mozilla Public License, v. 2.0).
|
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
|
||||||
*/
|
|
||||||
module tanya.crypto.padding;
|
|
||||||
|
|
||||||
import tanya.memory;
|
|
||||||
import std.algorithm.iteration;
|
|
||||||
import std.typecons;
|
|
||||||
|
|
||||||
@nogc:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supported padding mode.
|
|
||||||
*
|
|
||||||
* See_Also:
|
|
||||||
* $(D_PSYMBOL applyPadding)
|
|
||||||
*/
|
|
||||||
enum Mode
|
|
||||||
{
|
|
||||||
zero,
|
|
||||||
pkcs7,
|
|
||||||
ansiX923,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* allocator = Allocator that should be used if the block should be extended
|
|
||||||
* or a new block should be added.
|
|
||||||
* input = Sequence that should be padded.
|
|
||||||
* mode = Padding mode.
|
|
||||||
* blockSize = Block size.
|
|
||||||
*
|
|
||||||
* Returns: The functions modifies the initial array and returns it.
|
|
||||||
*
|
|
||||||
* See_Also:
|
|
||||||
* $(D_PSYMBOL Mode)
|
|
||||||
*/
|
|
||||||
ubyte[] applyPadding(ref ubyte[] input,
|
|
||||||
in Mode mode,
|
|
||||||
in ushort blockSize,
|
|
||||||
Allocator allocator = defaultAllocator)
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(blockSize > 0 && blockSize <= 256);
|
|
||||||
assert(blockSize % 64 == 0);
|
|
||||||
assert(input.length > 0);
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
immutable rest = cast(ubyte) input.length % blockSize;
|
|
||||||
immutable size_t lastBlock = input.length - (rest > 0 ? rest : blockSize);
|
|
||||||
immutable needed = cast(ubyte) (rest > 0 ? blockSize - rest : 0);
|
|
||||||
|
|
||||||
final switch (mode) with (Mode)
|
|
||||||
{
|
|
||||||
case zero:
|
|
||||||
allocator.expandArray(input, needed);
|
|
||||||
break;
|
|
||||||
case pkcs7:
|
|
||||||
if (needed)
|
|
||||||
{
|
|
||||||
allocator.expandArray(input, needed);
|
|
||||||
input[input.length - needed ..$].each!((ref e) => e = needed);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
allocator.expandArray(input, blockSize);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ansiX923:
|
|
||||||
allocator.expandArray(input, needed ? needed : blockSize);
|
|
||||||
input[$ - 1] = needed;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
{ // Zeros
|
|
||||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
|
||||||
|
|
||||||
applyPadding(input, Mode.zero, 64);
|
|
||||||
assert(input.length == 64);
|
|
||||||
|
|
||||||
applyPadding(input, Mode.zero, 64);
|
|
||||||
assert(input.length == 64);
|
|
||||||
assert(input[63] == 0);
|
|
||||||
|
|
||||||
defaultAllocator.finalize(input);
|
|
||||||
}
|
|
||||||
{ // PKCS#7
|
|
||||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
|
||||||
for (ubyte i; i < 40; ++i)
|
|
||||||
{
|
|
||||||
input[i] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyPadding(input, Mode.pkcs7, 64);
|
|
||||||
assert(input.length == 64);
|
|
||||||
for (ubyte i; i < 64; ++i)
|
|
||||||
{
|
|
||||||
if (i >= 40 && i < 50)
|
|
||||||
{
|
|
||||||
assert(input[i] == 0);
|
|
||||||
}
|
|
||||||
else if (i >= 50)
|
|
||||||
{
|
|
||||||
assert(input[i] == 14);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
assert(input[i] == i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyPadding(input, Mode.pkcs7, 64);
|
|
||||||
assert(input.length == 128);
|
|
||||||
for (ubyte i; i < 128; ++i)
|
|
||||||
{
|
|
||||||
if (i >= 64 || (i >= 40 && i < 50))
|
|
||||||
{
|
|
||||||
assert(input[i] == 0);
|
|
||||||
}
|
|
||||||
else if (i >= 50 && i < 64)
|
|
||||||
{
|
|
||||||
assert(input[i] == 14);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
assert(input[i] == i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultAllocator.finalize(input);
|
|
||||||
}
|
|
||||||
{ // ANSI X.923
|
|
||||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
|
||||||
for (ubyte i; i < 40; ++i)
|
|
||||||
{
|
|
||||||
input[i] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyPadding(input, Mode.ansiX923, 64);
|
|
||||||
assert(input.length == 64);
|
|
||||||
for (ubyte i; i < 64; ++i)
|
|
||||||
{
|
|
||||||
if (i < 40)
|
|
||||||
{
|
|
||||||
assert(input[i] == i);
|
|
||||||
}
|
|
||||||
else if (i == 63)
|
|
||||||
{
|
|
||||||
assert(input[i] == 14);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
assert(input[i] == 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyPadding(input, Mode.pkcs7, 64);
|
|
||||||
assert(input.length == 128);
|
|
||||||
for (ubyte i = 0; i < 128; ++i)
|
|
||||||
{
|
|
||||||
if (i < 40)
|
|
||||||
{
|
|
||||||
assert(input[i] == i);
|
|
||||||
}
|
|
||||||
else if (i == 63)
|
|
||||||
{
|
|
||||||
assert(input[i] == 14);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
assert(input[i] == 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultAllocator.finalize(input);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,226 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright: Eugene Wissner 2016.
|
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
||||||
* Mozilla Public License, v. 2.0).
|
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
|
||||||
*/
|
|
||||||
module tanya.event.internal.epoll;
|
|
||||||
|
|
||||||
import tanya.event.config;
|
|
||||||
|
|
||||||
static if (UseEpoll):
|
|
||||||
|
|
||||||
public import core.sys.linux.epoll;
|
|
||||||
import tanya.event.internal.selector;
|
|
||||||
import tanya.event.protocol;
|
|
||||||
import tanya.event.transport;
|
|
||||||
import tanya.event.watcher;
|
|
||||||
import tanya.event.loop;
|
|
||||||
import tanya.container.list;
|
|
||||||
import tanya.memory;
|
|
||||||
import core.stdc.errno;
|
|
||||||
import core.sys.posix.fcntl;
|
|
||||||
import core.sys.posix.netinet.in_;
|
|
||||||
import core.time;
|
|
||||||
import std.algorithm.comparison;
|
|
||||||
|
|
||||||
@nogc:
|
|
||||||
|
|
||||||
extern (C) nothrow
|
|
||||||
{ // TODO: Make a pull request for Phobos to mark this extern functions as @nogc.
|
|
||||||
int epoll_create1(int __flags);
|
|
||||||
int epoll_ctl(int __epfd, int __op, int __fd, epoll_event *__event);
|
|
||||||
int epoll_wait(int __epfd, epoll_event *__events, int __maxevents, int __timeout);
|
|
||||||
int accept4(int, sockaddr*, socklen_t*, int flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum maxEvents = 128;
|
|
||||||
|
|
||||||
class EpollLoop : Loop
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
/**
|
|
||||||
* Initializes the loop.
|
|
||||||
*/
|
|
||||||
this()
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
|
|
||||||
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
epollEvents = makeArray!epoll_event(defaultAllocator, maxEvents).ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees loop internals.
|
|
||||||
*/
|
|
||||||
~this()
|
|
||||||
{
|
|
||||||
finalize(defaultAllocator, epollEvents);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should be called if the backend configuration changes.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* socket = Socket.
|
|
||||||
* oldEvents = The events were already set.
|
|
||||||
* events = The events should be set.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
|
||||||
*/
|
|
||||||
protected override bool modify(int socket, EventMask oldEvents, EventMask events)
|
|
||||||
{
|
|
||||||
int op = EPOLL_CTL_DEL;
|
|
||||||
epoll_event ev;
|
|
||||||
|
|
||||||
if (events == oldEvents)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (events && oldEvents)
|
|
||||||
{
|
|
||||||
op = EPOLL_CTL_MOD;
|
|
||||||
}
|
|
||||||
else if (events && !oldEvents)
|
|
||||||
{
|
|
||||||
op = EPOLL_CTL_ADD;
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.data.fd = socket;
|
|
||||||
ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0)
|
|
||||||
| (events & Event.write ? EPOLLOUT : 0)
|
|
||||||
| EPOLLET;
|
|
||||||
|
|
||||||
return epoll_ctl(fd, op, socket, &ev) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept incoming connections.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* protocolFactory = Protocol factory.
|
|
||||||
* socket = Socket.
|
|
||||||
*/
|
|
||||||
protected override void acceptConnection(Protocol delegate() @nogc protocolFactory,
|
|
||||||
int socket)
|
|
||||||
{
|
|
||||||
sockaddr_in client_addr;
|
|
||||||
socklen_t client_len = client_addr.sizeof;
|
|
||||||
int client = accept4(socket,
|
|
||||||
cast(sockaddr *)&client_addr,
|
|
||||||
&client_len,
|
|
||||||
O_NONBLOCK);
|
|
||||||
while (client >= 0)
|
|
||||||
{
|
|
||||||
auto transport = make!SocketTransport(defaultAllocator, this, client);
|
|
||||||
IOWatcher connection;
|
|
||||||
|
|
||||||
if (connections.length > client)
|
|
||||||
{
|
|
||||||
connection = cast(IOWatcher) connections[client];
|
|
||||||
// If it is a ConnectionWatcher
|
|
||||||
if (connection is null && connections[client] !is null)
|
|
||||||
{
|
|
||||||
finalize(defaultAllocator, connections[client]);
|
|
||||||
connections[client] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (connection !is null)
|
|
||||||
{
|
|
||||||
connection(protocolFactory, transport);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
connections[client] = make!IOWatcher(defaultAllocator,
|
|
||||||
protocolFactory,
|
|
||||||
transport);
|
|
||||||
}
|
|
||||||
|
|
||||||
modify(client, EventMask(Event.none), EventMask(Event.read, Event.write));
|
|
||||||
|
|
||||||
swapPendings.insertBack(connections[client]);
|
|
||||||
|
|
||||||
client = accept4(socket,
|
|
||||||
cast(sockaddr *)&client_addr,
|
|
||||||
&client_len,
|
|
||||||
O_NONBLOCK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the actual polling.
|
|
||||||
*/
|
|
||||||
protected override void poll()
|
|
||||||
{
|
|
||||||
// Don't block
|
|
||||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
|
||||||
auto eventCount = epoll_wait(fd, epollEvents, maxEvents, timeout);
|
|
||||||
|
|
||||||
if (eventCount < 0)
|
|
||||||
{
|
|
||||||
if (errno != EINTR)
|
|
||||||
{
|
|
||||||
throw make!BadLoopException(defaultAllocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto i = 0; i < eventCount; ++i)
|
|
||||||
{
|
|
||||||
epoll_event *ev = epollEvents + i;
|
|
||||||
auto connection = cast(IOWatcher) connections[ev.data.fd];
|
|
||||||
|
|
||||||
if (connection is null)
|
|
||||||
{
|
|
||||||
swapPendings.insertBack(connections[ev.data.fd]);
|
|
||||||
// acceptConnection(connections[ev.data.fd].protocol,
|
|
||||||
// connections[ev.data.fd].socket);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto transport = cast(SocketTransport) connection.transport;
|
|
||||||
assert(transport !is null);
|
|
||||||
|
|
||||||
if (ev.events & (EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (!transport.receive())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
swapPendings.insertBack(connection);
|
|
||||||
}
|
|
||||||
catch (TransportException e)
|
|
||||||
{
|
|
||||||
swapPendings.insertBack(connection);
|
|
||||||
finalize(defaultAllocator, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (ev.events & (EPOLLOUT | EPOLLERR | EPOLLHUP))
|
|
||||||
{
|
|
||||||
transport.writeReady = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: The blocking time.
|
|
||||||
*/
|
|
||||||
override protected @property inout(Duration) blockTime()
|
|
||||||
inout @safe pure nothrow
|
|
||||||
{
|
|
||||||
return min(super.blockTime, 1.dur!"seconds");
|
|
||||||
}
|
|
||||||
|
|
||||||
private int fd;
|
|
||||||
private epoll_event* epollEvents;
|
|
||||||
}
|
|
@ -1,217 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright: Eugene Wissner 2016.
|
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
||||||
* Mozilla Public License, v. 2.0).
|
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
|
||||||
*/
|
|
||||||
module tanya.event.internal.selector;
|
|
||||||
|
|
||||||
import tanya.memory;
|
|
||||||
import tanya.container.buffer;
|
|
||||||
import tanya.event.loop;
|
|
||||||
import tanya.event.protocol;
|
|
||||||
import tanya.event.transport;
|
|
||||||
import core.stdc.errno;
|
|
||||||
import core.sys.posix.netinet.in_;
|
|
||||||
import core.sys.posix.unistd;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transport for stream sockets.
|
|
||||||
*/
|
|
||||||
class SocketTransport : DuplexTransport
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
private int socket_ = -1;
|
|
||||||
|
|
||||||
private Protocol protocol_;
|
|
||||||
|
|
||||||
/// Input buffer.
|
|
||||||
private WriteBuffer input_;
|
|
||||||
|
|
||||||
/// Output buffer.
|
|
||||||
private ReadBuffer output_;
|
|
||||||
|
|
||||||
private Loop loop;
|
|
||||||
|
|
||||||
private bool disconnected_;
|
|
||||||
|
|
||||||
package bool writeReady;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* loop = Event loop.
|
|
||||||
* socket = Socket.
|
|
||||||
* protocol = Protocol.
|
|
||||||
*/
|
|
||||||
this(Loop loop, int socket, Protocol protocol = null)
|
|
||||||
{
|
|
||||||
socket_ = socket;
|
|
||||||
protocol_ = protocol;
|
|
||||||
this.loop = loop;
|
|
||||||
input_ = make!WriteBuffer(defaultAllocator);
|
|
||||||
output_ = make!ReadBuffer(defaultAllocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the transport and deallocate the data buffers.
|
|
||||||
*/
|
|
||||||
~this()
|
|
||||||
{
|
|
||||||
close(socket);
|
|
||||||
finalize(defaultAllocator, input_);
|
|
||||||
finalize(defaultAllocator, output_);
|
|
||||||
finalize(defaultAllocator, protocol_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Transport socket.
|
|
||||||
*/
|
|
||||||
int socket() const @safe pure nothrow
|
|
||||||
{
|
|
||||||
return socket_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Protocol.
|
|
||||||
*/
|
|
||||||
@property Protocol protocol() @safe pure nothrow
|
|
||||||
{
|
|
||||||
return protocol_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: $(D_KEYWORD true) if the remote peer closed the connection,
|
|
||||||
* $(D_KEYWORD false) otherwise.
|
|
||||||
*/
|
|
||||||
@property immutable(bool) disconnected() const @safe pure nothrow
|
|
||||||
{
|
|
||||||
return disconnected_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* protocol = Application protocol.
|
|
||||||
*/
|
|
||||||
@property void protocol(Protocol protocol) @safe pure nothrow
|
|
||||||
{
|
|
||||||
protocol_ = protocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Application protocol.
|
|
||||||
*/
|
|
||||||
@property inout(Protocol) protocol() inout @safe pure nothrow
|
|
||||||
{
|
|
||||||
return protocol_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write some data to the transport.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* data = Data to send.
|
|
||||||
*/
|
|
||||||
void write(ubyte[] data)
|
|
||||||
{
|
|
||||||
// If the buffer wasn't empty the transport should be already there.
|
|
||||||
if (!input.length && data.length)
|
|
||||||
{
|
|
||||||
loop.feed(this);
|
|
||||||
}
|
|
||||||
input ~= data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Input buffer.
|
|
||||||
*/
|
|
||||||
@property WriteBuffer input() @safe pure nothrow
|
|
||||||
{
|
|
||||||
return input_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Output buffer.
|
|
||||||
*/
|
|
||||||
@property ReadBuffer output() @safe pure nothrow
|
|
||||||
{
|
|
||||||
return output_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read data from the socket. Returns $(D_KEYWORD true) if the reading
|
|
||||||
* is completed. In the case that the peer closed the connection, returns
|
|
||||||
* $(D_KEYWORD true) aswell.
|
|
||||||
*
|
|
||||||
* Returns: Whether the reading is completed.
|
|
||||||
*
|
|
||||||
* Throws: $(D_PSYMBOL TransportException) if a read error is occured.
|
|
||||||
*/
|
|
||||||
bool receive()
|
|
||||||
{
|
|
||||||
auto readCount = recv(socket, output.buffer, output.free, 0);
|
|
||||||
|
|
||||||
if (readCount > 0)
|
|
||||||
{
|
|
||||||
output_ ~= output.buffer[0..readCount];
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (readCount == 0)
|
|
||||||
{
|
|
||||||
disconnected_ = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (errno == EAGAIN || errno == EWOULDBLOCK)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
disconnected_ = true;
|
|
||||||
throw make!TransportException(defaultAllocator,
|
|
||||||
"Read from the socket failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Whether the writing is completed.
|
|
||||||
*
|
|
||||||
* Throws: $(D_PSYMBOL TransportException) if a read error is occured.
|
|
||||||
*/
|
|
||||||
bool send()
|
|
||||||
{
|
|
||||||
auto sentCount = core.sys.posix.netinet.in_.send(socket,
|
|
||||||
input.buffer,
|
|
||||||
input.length,
|
|
||||||
0);
|
|
||||||
|
|
||||||
input.written = sentCount;
|
|
||||||
if (input.length == 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (sentCount >= 0)
|
|
||||||
{
|
|
||||||
loop.feed(this);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (errno == EAGAIN || errno == EWOULDBLOCK)
|
|
||||||
{
|
|
||||||
writeReady = false;
|
|
||||||
loop.feed(this);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
disconnected_ = true;
|
|
||||||
loop.feed(this);
|
|
||||||
throw make!TransportException(defaultAllocator,
|
|
||||||
"Write to the socket failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,279 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright: Eugene Wissner 2016.
|
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
||||||
* Mozilla Public License, v. 2.0).
|
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
|
||||||
*/
|
|
||||||
module tanya.event.loop;
|
|
||||||
|
|
||||||
import tanya.memory;
|
|
||||||
import tanya.container.queue;
|
|
||||||
import tanya.container.vector;
|
|
||||||
import tanya.event.config;
|
|
||||||
import tanya.event.protocol;
|
|
||||||
import tanya.event.transport;
|
|
||||||
import tanya.event.watcher;
|
|
||||||
import tanya.container.buffer;
|
|
||||||
import core.thread;
|
|
||||||
import core.time;
|
|
||||||
import std.algorithm.iteration;
|
|
||||||
import std.algorithm.mutation;
|
|
||||||
import std.typecons;
|
|
||||||
|
|
||||||
static if (UseEpoll)
|
|
||||||
{
|
|
||||||
import tanya.event.internal.epoll;
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Events.
|
|
||||||
*/
|
|
||||||
enum Event : uint
|
|
||||||
{
|
|
||||||
none = 0x00, /// No events.
|
|
||||||
read = 0x01, /// Non-blocking read call.
|
|
||||||
write = 0x02, /// Non-blocking write call.
|
|
||||||
accept = 0x04, /// Connection made.
|
|
||||||
}
|
|
||||||
|
|
||||||
alias EventMask = BitFlags!Event;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event loop.
|
|
||||||
*/
|
|
||||||
abstract class Loop
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
/// Pending watchers.
|
|
||||||
protected Queue!Watcher pendings;
|
|
||||||
|
|
||||||
protected Queue!Watcher swapPendings;
|
|
||||||
|
|
||||||
/// Pending connections.
|
|
||||||
protected Vector!ConnectionWatcher connections;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the loop.
|
|
||||||
*/
|
|
||||||
this()
|
|
||||||
{
|
|
||||||
connections = make!(Vector!ConnectionWatcher)(defaultAllocator);
|
|
||||||
pendings = make!(Queue!Watcher)(defaultAllocator);
|
|
||||||
swapPendings = make!(Queue!Watcher)(defaultAllocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees loop internals.
|
|
||||||
*/
|
|
||||||
~this()
|
|
||||||
{
|
|
||||||
finalize(defaultAllocator, connections);
|
|
||||||
finalize(defaultAllocator, pendings);
|
|
||||||
finalize(defaultAllocator, swapPendings);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the loop.
|
|
||||||
*/
|
|
||||||
void run()
|
|
||||||
{
|
|
||||||
done_ = false;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
poll();
|
|
||||||
|
|
||||||
// Invoke pendings
|
|
||||||
swapPendings.each!((ref p) => p.invoke());
|
|
||||||
|
|
||||||
swap(pendings, swapPendings);
|
|
||||||
}
|
|
||||||
while (!done_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Break out of the loop.
|
|
||||||
*/
|
|
||||||
void unloop() @safe pure nothrow
|
|
||||||
{
|
|
||||||
done_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start watching.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* watcher = Watcher.
|
|
||||||
*/
|
|
||||||
void start(ConnectionWatcher watcher)
|
|
||||||
{
|
|
||||||
if (watcher.active)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
watcher.active = true;
|
|
||||||
watcher.accept = &acceptConnection;
|
|
||||||
connections[watcher.socket] = watcher;
|
|
||||||
|
|
||||||
modify(watcher.socket, EventMask(Event.none), EventMask(Event.accept));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop watching.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* watcher = Watcher.
|
|
||||||
*/
|
|
||||||
void stop(ConnectionWatcher watcher)
|
|
||||||
{
|
|
||||||
if (!watcher.active)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
watcher.active = false;
|
|
||||||
|
|
||||||
modify(watcher.socket, EventMask(Event.accept), EventMask(Event.none));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Feeds the given event set into the event loop, as if the specified event
|
|
||||||
* had happened for the specified watcher.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* transport = Affected transport.
|
|
||||||
*/
|
|
||||||
void feed(DuplexTransport transport)
|
|
||||||
{
|
|
||||||
pendings.insertBack(connections[transport.socket]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should be called if the backend configuration changes.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* socket = Socket.
|
|
||||||
* oldEvents = The events were already set.
|
|
||||||
* events = The events should be set.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
|
||||||
*/
|
|
||||||
protected bool modify(int socket, EventMask oldEvents, EventMask events);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: The blocking time.
|
|
||||||
*/
|
|
||||||
protected @property inout(Duration) blockTime()
|
|
||||||
inout @safe pure nothrow
|
|
||||||
{
|
|
||||||
// Don't block if we have to do.
|
|
||||||
return swapPendings.empty ? blockTime_ : Duration.zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the blocking time for IO watchers.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* blockTime = The blocking time. Cannot be larger than
|
|
||||||
* $(D_PSYMBOL maxBlockTime).
|
|
||||||
*/
|
|
||||||
protected @property void blockTime(in Duration blockTime) @safe pure nothrow
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
|
|
||||||
assert(!blockTime.isNegative);
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
blockTime_ = blockTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the actual polling.
|
|
||||||
*/
|
|
||||||
protected void poll();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept incoming connections.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* protocolFactory = Protocol factory.
|
|
||||||
* socket = Socket.
|
|
||||||
*/
|
|
||||||
protected void acceptConnection(Protocol delegate() @nogc protocolFactory,
|
|
||||||
int socket);
|
|
||||||
|
|
||||||
/// Whether the event loop should be stopped.
|
|
||||||
private bool done_;
|
|
||||||
|
|
||||||
/// Maximal block time.
|
|
||||||
protected Duration blockTime_ = 1.dur!"minutes";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown on errors in the event loop.
|
|
||||||
*/
|
|
||||||
class BadLoopException : Exception
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* file = The file where the exception occurred.
|
|
||||||
* line = The line number where the exception occurred.
|
|
||||||
* next = The previous exception in the chain of exceptions, if any.
|
|
||||||
*/
|
|
||||||
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
|
|
||||||
pure @safe nothrow const
|
|
||||||
{
|
|
||||||
super("Event loop cannot be initialized.", file, line, next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the event loop used by default. If an event loop wasn't set with
|
|
||||||
* $(D_PSYMBOL defaultLoop) before, $(D_PSYMBOL getDefaultLoop()) will try to
|
|
||||||
* choose an event loop supported on the system.
|
|
||||||
*
|
|
||||||
* Returns: The default event loop.
|
|
||||||
*/
|
|
||||||
Loop getDefaultLoop()
|
|
||||||
{
|
|
||||||
if (_defaultLoop !is null)
|
|
||||||
{
|
|
||||||
return _defaultLoop;
|
|
||||||
}
|
|
||||||
|
|
||||||
static if (UseEpoll)
|
|
||||||
{
|
|
||||||
_defaultLoop = make!EpollLoop(defaultAllocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _defaultLoop;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default event loop.
|
|
||||||
*
|
|
||||||
* This property makes it possible to implement your own backends or event
|
|
||||||
* loops, for example, if the system is not supported or if you want to
|
|
||||||
* extend the supported implementation. Just extend $(D_PSYMBOL Loop) and pass
|
|
||||||
* your implementation to this property.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* loop = The event loop.
|
|
||||||
*/
|
|
||||||
@property void defaultLoop(Loop loop)
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(loop !is null);
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
_defaultLoop = loop;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Loop _defaultLoop;
|
|
@ -1,138 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright: Eugene Wissner 2016.
|
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
||||||
* Mozilla Public License, v. 2.0).
|
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
|
||||||
*/
|
|
||||||
module tanya.event.transport;
|
|
||||||
|
|
||||||
import tanya.container.buffer;
|
|
||||||
import tanya.event.protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown on read/write errors.
|
|
||||||
*/
|
|
||||||
class TransportException : Exception
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* msg = Message to output.
|
|
||||||
* file = The file where the exception occurred.
|
|
||||||
* line = The line number where the exception occurred.
|
|
||||||
* next = The previous exception in the chain of exceptions, if any.
|
|
||||||
*/
|
|
||||||
this(string msg,
|
|
||||||
string file = __FILE__,
|
|
||||||
size_t line = __LINE__,
|
|
||||||
Throwable next = null) pure @safe nothrow const
|
|
||||||
{
|
|
||||||
super(msg, file, line, next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base transport interface.
|
|
||||||
*/
|
|
||||||
interface Transport
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
/**
|
|
||||||
* Returns: Protocol.
|
|
||||||
*/
|
|
||||||
@property Protocol protocol() @safe pure nothrow;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: $(D_KEYWORD true) if the peer closed the connection,
|
|
||||||
* $(D_KEYWORD false) otherwise.
|
|
||||||
*/
|
|
||||||
@property immutable(bool) disconnected() const @safe pure nothrow;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* protocol = Application protocol.
|
|
||||||
*/
|
|
||||||
@property void protocol(Protocol protocol) @safe pure nothrow
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(protocol !is null, "protocolConnected cannot be unset.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Application protocol.
|
|
||||||
*/
|
|
||||||
@property inout(Protocol) protocol() inout @safe pure nothrow;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Transport socket.
|
|
||||||
*/
|
|
||||||
int socket() const @safe pure nothrow;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for read-only transports.
|
|
||||||
*/
|
|
||||||
interface ReadTransport : Transport
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
/**
|
|
||||||
* Returns: Underlying output buffer.
|
|
||||||
*/
|
|
||||||
@property ReadBuffer output();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads data into the buffer.
|
|
||||||
*
|
|
||||||
* Returns: Whether the reading is completed.
|
|
||||||
*
|
|
||||||
* Throws: $(D_PSYMBOL TransportException) if a read error is occured.
|
|
||||||
*/
|
|
||||||
bool receive()
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(!disconnected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for write-only transports.
|
|
||||||
*/
|
|
||||||
interface WriteTransport : Transport
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
/**
|
|
||||||
* Returns: Underlying input buffer.
|
|
||||||
*/
|
|
||||||
@property WriteBuffer input();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write some data to the transport.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* data = Data to send.
|
|
||||||
*/
|
|
||||||
void write(ubyte[] data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Whether the writing is completed.
|
|
||||||
*
|
|
||||||
* Throws: $(D_PSYMBOL TransportException) if a read error is occured.
|
|
||||||
*/
|
|
||||||
bool send()
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(input.length);
|
|
||||||
assert(!disconnected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a bidirectional transport.
|
|
||||||
*/
|
|
||||||
abstract class DuplexTransport : ReadTransport, WriteTransport
|
|
||||||
{
|
|
||||||
}
|
|
@ -1,210 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright: Eugene Wissner 2016.
|
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
||||||
* Mozilla Public License, v. 2.0).
|
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
|
||||||
*/
|
|
||||||
module tanya.event.watcher;
|
|
||||||
|
|
||||||
import tanya.event.protocol;
|
|
||||||
import tanya.event.transport;
|
|
||||||
import tanya.memory;
|
|
||||||
import std.functional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A watcher is an opaque structure that you allocate and register to record
|
|
||||||
* your interest in some event.
|
|
||||||
*/
|
|
||||||
abstract class Watcher
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
/// Whether the watcher is active.
|
|
||||||
bool active;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke some action on event.
|
|
||||||
*/
|
|
||||||
void invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConnectionWatcher : Watcher
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
/// Watched file descriptor.
|
|
||||||
private int socket_;
|
|
||||||
|
|
||||||
/// Protocol factory.
|
|
||||||
protected Protocol delegate() protocolFactory;
|
|
||||||
|
|
||||||
/// Callback.
|
|
||||||
package void delegate(Protocol delegate() @nogc protocolFactory,
|
|
||||||
int socket) accept;
|
|
||||||
|
|
||||||
invariant
|
|
||||||
{
|
|
||||||
assert(socket_ >= 0, "Called with negative file descriptor.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* protocolFactory = Function returning a new $(D_PSYMBOL Protocol) instance.
|
|
||||||
* socket = Socket.
|
|
||||||
*/
|
|
||||||
this(Protocol function() @nogc protocolFactory, int socket)
|
|
||||||
{
|
|
||||||
this.protocolFactory = toDelegate(protocolFactory);
|
|
||||||
socket_ = socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ditto.
|
|
||||||
this(Protocol delegate() @nogc protocolFactory, int socket)
|
|
||||||
{
|
|
||||||
this.protocolFactory = protocolFactory;
|
|
||||||
socket_ = socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ditto.
|
|
||||||
protected this(Protocol function() @nogc protocolFactory)
|
|
||||||
{
|
|
||||||
this.protocolFactory = toDelegate(protocolFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ditto.
|
|
||||||
protected this(Protocol delegate() @nogc protocolFactory)
|
|
||||||
{
|
|
||||||
this.protocolFactory = protocolFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Socket.
|
|
||||||
*/
|
|
||||||
@property inout(int) socket() inout @safe pure nothrow
|
|
||||||
{
|
|
||||||
return socket_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Application protocol factory.
|
|
||||||
*/
|
|
||||||
@property inout(Protocol delegate() @nogc) protocol() inout
|
|
||||||
{
|
|
||||||
return protocolFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void invoke()
|
|
||||||
{
|
|
||||||
accept(protocol, socket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains a pending watcher with the invoked events or a transport can be
|
|
||||||
* read from.
|
|
||||||
*/
|
|
||||||
class IOWatcher : ConnectionWatcher
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
/// References a watcher or a transport.
|
|
||||||
DuplexTransport transport_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* protocolFactory = Function returning application specific protocol.
|
|
||||||
* transport = Transport.
|
|
||||||
*/
|
|
||||||
this(Protocol delegate() @nogc protocolFactory,
|
|
||||||
DuplexTransport transport)
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(transport !is null);
|
|
||||||
assert(protocolFactory !is null);
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
super(protocolFactory);
|
|
||||||
this.transport_ = transport;
|
|
||||||
}
|
|
||||||
|
|
||||||
~this()
|
|
||||||
{
|
|
||||||
finalize(defaultAllocator, transport_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns a transport.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* protocolFactory = Function returning application specific protocol.
|
|
||||||
* transport = Transport.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD this).
|
|
||||||
*/
|
|
||||||
IOWatcher opCall(Protocol delegate() @nogc protocolFactory,
|
|
||||||
DuplexTransport transport) @safe pure nothrow
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(transport !is null);
|
|
||||||
assert(protocolFactory !is null);
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
this.protocolFactory = protocolFactory;
|
|
||||||
this.transport_ = transport;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Transport used by this watcher.
|
|
||||||
*/
|
|
||||||
@property inout(DuplexTransport) transport() inout @safe pure nothrow
|
|
||||||
{
|
|
||||||
return transport_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Socket.
|
|
||||||
*/
|
|
||||||
override @property inout(int) socket() inout @safe pure nothrow
|
|
||||||
{
|
|
||||||
return transport.socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the watcher callback.
|
|
||||||
*
|
|
||||||
* Finalizes the transport on disconnect.
|
|
||||||
*/
|
|
||||||
override void invoke()
|
|
||||||
{
|
|
||||||
if (transport.protocol is null)
|
|
||||||
{
|
|
||||||
transport.protocol = protocolFactory();
|
|
||||||
transport.protocol.connected(transport);
|
|
||||||
}
|
|
||||||
else if (transport.disconnected)
|
|
||||||
{
|
|
||||||
transport.protocol.disconnected();
|
|
||||||
finalize(defaultAllocator, transport_);
|
|
||||||
protocolFactory = null;
|
|
||||||
}
|
|
||||||
else if (transport.output.length)
|
|
||||||
{
|
|
||||||
transport.protocol.received(transport.output[]);
|
|
||||||
}
|
|
||||||
else if (transport.input.length)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
transport.send();
|
|
||||||
}
|
|
||||||
catch (TransportException e)
|
|
||||||
{
|
|
||||||
finalize(defaultAllocator, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,112 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright: Eugene Wissner 2016.
|
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
||||||
* Mozilla Public License, v. 2.0).
|
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
|
||||||
*/
|
|
||||||
module tanya.container.math;
|
|
||||||
|
|
||||||
version (unittest)
|
|
||||||
{
|
|
||||||
import std.algorithm.iteration;
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* x = Base.
|
|
||||||
* y = Exponent.
|
|
||||||
* z = Divisor.
|
|
||||||
*
|
|
||||||
* Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y)
|
|
||||||
* by $(D_PARAM z).
|
|
||||||
*/
|
|
||||||
ulong pow(ulong x, ulong y, ulong z) @safe nothrow pure
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(z > 0);
|
|
||||||
}
|
|
||||||
out (result)
|
|
||||||
{
|
|
||||||
assert(result >= 0);
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
ulong mask = ulong.max / 2 + 1, result;
|
|
||||||
|
|
||||||
if (y == 0)
|
|
||||||
{
|
|
||||||
return 1 % z;
|
|
||||||
}
|
|
||||||
else if (y == 1)
|
|
||||||
{
|
|
||||||
return x % z;
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
auto bit = y & mask;
|
|
||||||
if (!result && bit)
|
|
||||||
{
|
|
||||||
result = x;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
result *= result;
|
|
||||||
if (bit)
|
|
||||||
{
|
|
||||||
result *= x;
|
|
||||||
}
|
|
||||||
result %= z;
|
|
||||||
|
|
||||||
}
|
|
||||||
while (mask >>= 1);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
assert(pow(3, 5, 7) == 5);
|
|
||||||
assert(pow(2, 2, 1) == 0);
|
|
||||||
assert(pow(3, 3, 3) == 0);
|
|
||||||
assert(pow(7, 4, 2) == 1);
|
|
||||||
assert(pow(53, 0, 2) == 1);
|
|
||||||
assert(pow(53, 1, 3) == 2);
|
|
||||||
assert(pow(53, 2, 5) == 4);
|
|
||||||
assert(pow(0, 0, 5) == 1);
|
|
||||||
assert(pow(0, 5, 5) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if $(D_PARAM x) is a prime.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* x = The number should be checked.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number,
|
|
||||||
* $(D_KEYWORD false) otherwise.
|
|
||||||
*/
|
|
||||||
bool isPseudoprime(ulong x) @safe nothrow pure
|
|
||||||
{
|
|
||||||
return pow(2, x - 1, x) == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719,
|
|
||||||
74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821,
|
|
||||||
74827, 9973, 104729, 15485867, 49979693, 104395303,
|
|
||||||
593441861, 104729, 15485867, 49979693, 104395303,
|
|
||||||
593441861, 899809363, 982451653];
|
|
||||||
|
|
||||||
known.each!((ref x) => assert(isPseudoprime(x)));
|
|
||||||
}
|
|
1060
source/tanya/math/mp.d
Normal file
1060
source/tanya/math/mp.d
Normal file
File diff suppressed because it is too large
Load Diff
172
source/tanya/math/package.d
Normal file
172
source/tanya/math/package.d
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright: Eugene Wissner 2016.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||||
|
*/
|
||||||
|
module tanya.math;
|
||||||
|
|
||||||
|
import std.traits;
|
||||||
|
public import tanya.math.mp;
|
||||||
|
public import tanya.math.random;
|
||||||
|
|
||||||
|
version (unittest)
|
||||||
|
{
|
||||||
|
import std.algorithm.iteration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z).
|
||||||
|
*
|
||||||
|
* If $(D_PARAM I) is an $(D_PSYMBOL Integer), the allocator of $(D_PARAM x)
|
||||||
|
* is used to allocate the result.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* I = Base type.
|
||||||
|
* G = Exponent type.
|
||||||
|
* H = Divisor type:
|
||||||
|
* x = Base.
|
||||||
|
* y = Exponent.
|
||||||
|
* z = Divisor.
|
||||||
|
*
|
||||||
|
* Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y)
|
||||||
|
* by $(D_PARAM z).
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE z > 0)
|
||||||
|
*/
|
||||||
|
H pow(I, G, H)(in auto ref I x, in auto ref G y, in auto ref H z)
|
||||||
|
if (isIntegral!I && isIntegral!G && isIntegral!H)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(z > 0, "Division by zero.");
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
G mask = G.max / 2 + 1;
|
||||||
|
H result;
|
||||||
|
|
||||||
|
if (y == 0)
|
||||||
|
{
|
||||||
|
return 1 % z;
|
||||||
|
}
|
||||||
|
else if (y == 1)
|
||||||
|
{
|
||||||
|
return x % z;
|
||||||
|
}
|
||||||
|
do
|
||||||
|
{
|
||||||
|
immutable bit = y & mask;
|
||||||
|
if (!result && bit)
|
||||||
|
{
|
||||||
|
result = x;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result *= result;
|
||||||
|
if (bit)
|
||||||
|
{
|
||||||
|
result *= x;
|
||||||
|
}
|
||||||
|
result %= z;
|
||||||
|
}
|
||||||
|
while (mask >>= 1);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
I pow(I)(in auto ref I x, in auto ref I y, in auto ref I z)
|
||||||
|
if (is(I == Integer))
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(z.length > 0, "Division by zero.");
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
size_t i = y.length;
|
||||||
|
auto tmp2 = Integer(x.allocator), tmp1 = Integer(x, x.allocator);
|
||||||
|
Integer result = Integer(x.allocator);
|
||||||
|
|
||||||
|
if (x.length == 0 && i != 0)
|
||||||
|
{
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
while (i)
|
||||||
|
{
|
||||||
|
--i;
|
||||||
|
for (ubyte mask = 0x01; mask; mask <<= 1)
|
||||||
|
{
|
||||||
|
if (y.rep[i] & mask)
|
||||||
|
{
|
||||||
|
result *= tmp1;
|
||||||
|
result %= z;
|
||||||
|
}
|
||||||
|
tmp2 = tmp1;
|
||||||
|
tmp1 *= tmp2;
|
||||||
|
tmp1 %= z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pure nothrow @safe @nogc unittest
|
||||||
|
{
|
||||||
|
assert(pow(3, 5, 7) == 5);
|
||||||
|
assert(pow(2, 2, 1) == 0);
|
||||||
|
assert(pow(3, 3, 3) == 0);
|
||||||
|
assert(pow(7, 4, 2) == 1);
|
||||||
|
assert(pow(53, 0, 2) == 1);
|
||||||
|
assert(pow(53, 1, 3) == 2);
|
||||||
|
assert(pow(53, 2, 5) == 4);
|
||||||
|
assert(pow(0, 0, 5) == 1);
|
||||||
|
assert(pow(0, 5, 5) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
assert(cast(long) pow(Integer(3), Integer(5), Integer(7)) == 5);
|
||||||
|
assert(cast(long) pow(Integer(2), Integer(2), Integer(1)) == 0);
|
||||||
|
assert(cast(long) pow(Integer(3), Integer(3), Integer(3)) == 0);
|
||||||
|
assert(cast(long) pow(Integer(7), Integer(4), Integer(2)) == 1);
|
||||||
|
assert(cast(long) pow(Integer(53), Integer(0), Integer(2)) == 1);
|
||||||
|
assert(cast(long) pow(Integer(53), Integer(1), Integer(3)) == 2);
|
||||||
|
assert(cast(long) pow(Integer(53), Integer(2), Integer(5)) == 4);
|
||||||
|
assert(cast(long) pow(Integer(0), Integer(0), Integer(5)) == 1);
|
||||||
|
assert(cast(long) pow(Integer(0), Integer(5), Integer(5)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if $(D_PARAM x) is a prime.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* x = The number should be checked.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
|
bool isPseudoprime(ulong x) nothrow pure @safe @nogc
|
||||||
|
{
|
||||||
|
return pow(2, x - 1, x) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719,
|
||||||
|
74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821,
|
||||||
|
74827, 9973, 104729, 15485867, 49979693, 104395303,
|
||||||
|
593441861, 104729, 15485867, 49979693, 104395303,
|
||||||
|
593441861, 899809363, 982451653];
|
||||||
|
|
||||||
|
known.each!((ref x) => assert(isPseudoprime(x)));
|
||||||
|
}
|
@ -3,18 +3,18 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Random number generator.
|
||||||
|
*
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.random;
|
module tanya.math.random;
|
||||||
|
|
||||||
import tanya.memory;
|
|
||||||
import std.digest.sha;
|
import std.digest.sha;
|
||||||
import std.typecons;
|
import std.typecons;
|
||||||
|
import tanya.memory;
|
||||||
@nogc:
|
|
||||||
|
|
||||||
/// Block size of entropy accumulator (SHA-512).
|
/// Block size of entropy accumulator (SHA-512).
|
||||||
enum blockSize = 64;
|
enum blockSize = 64;
|
||||||
@ -27,7 +27,6 @@ enum maxGather = 128;
|
|||||||
*/
|
*/
|
||||||
class EntropyException : Exception
|
class EntropyException : Exception
|
||||||
{
|
{
|
||||||
@nogc:
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* msg = Message to output.
|
* msg = Message to output.
|
||||||
@ -38,7 +37,7 @@ class EntropyException : Exception
|
|||||||
this(string msg,
|
this(string msg,
|
||||||
string file = __FILE__,
|
string file = __FILE__,
|
||||||
size_t line = __LINE__,
|
size_t line = __LINE__,
|
||||||
Throwable next = null) pure @safe nothrow const
|
Throwable next = null) pure @safe nothrow const @nogc
|
||||||
{
|
{
|
||||||
super(msg, file, line, next);
|
super(msg, file, line, next);
|
||||||
}
|
}
|
||||||
@ -49,7 +48,6 @@ class EntropyException : Exception
|
|||||||
*/
|
*/
|
||||||
abstract class EntropySource
|
abstract class EntropySource
|
||||||
{
|
{
|
||||||
@nogc:
|
|
||||||
/// Amount of already generated entropy.
|
/// Amount of already generated entropy.
|
||||||
protected ushort size_;
|
protected ushort size_;
|
||||||
|
|
||||||
@ -103,7 +101,6 @@ version (linux)
|
|||||||
*/
|
*/
|
||||||
class PlatformEntropySource : EntropySource
|
class PlatformEntropySource : EntropySource
|
||||||
{
|
{
|
||||||
@nogc:
|
|
||||||
/**
|
/**
|
||||||
* Returns: Minimum bytes required from the entropy source.
|
* Returns: Minimum bytes required from the entropy source.
|
||||||
*/
|
*/
|
||||||
@ -153,7 +150,7 @@ version (linux)
|
|||||||
/**
|
/**
|
||||||
* Pseudorandom number generator.
|
* Pseudorandom number generator.
|
||||||
* ---
|
* ---
|
||||||
* auto entropy = defaultAllocator.make!Entropy;
|
* auto entropy = defaultAllocator.make!Entropy();
|
||||||
*
|
*
|
||||||
* ubyte[blockSize] output;
|
* ubyte[blockSize] output;
|
||||||
*
|
*
|
||||||
@ -164,13 +161,12 @@ version (linux)
|
|||||||
*/
|
*/
|
||||||
class Entropy
|
class Entropy
|
||||||
{
|
{
|
||||||
@nogc:
|
|
||||||
/// Entropy sources.
|
/// Entropy sources.
|
||||||
protected EntropySource[] sources;
|
protected EntropySource[] sources;
|
||||||
|
|
||||||
private ubyte sourceCount_;
|
private ubyte sourceCount_;
|
||||||
|
|
||||||
private Allocator allocator;
|
private shared Allocator allocator;
|
||||||
|
|
||||||
/// Entropy accumulator.
|
/// Entropy accumulator.
|
||||||
protected SHA!(maxGather * 8, 512) accumulator;
|
protected SHA!(maxGather * 8, 512) accumulator;
|
||||||
@ -181,7 +177,7 @@ class Entropy
|
|||||||
* allocator = Allocator to allocate entropy sources available on the
|
* allocator = Allocator to allocate entropy sources available on the
|
||||||
* system.
|
* system.
|
||||||
*/
|
*/
|
||||||
this(size_t maxSources = 20, Allocator allocator = defaultAllocator)
|
this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(maxSources > 0 && maxSources <= ubyte.max);
|
assert(maxSources > 0 && maxSources <= ubyte.max);
|
@ -3,32 +3,34 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.memory.allocator;
|
module tanya.memory.allocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface should be similar to $(D_PSYMBOL
|
* Abstract class implementing a basic allocator.
|
||||||
* std.experimental.allocator.IAllocator), but usable in
|
|
||||||
* $(D_KEYWORD @nogc)-code.
|
|
||||||
*/
|
*/
|
||||||
interface Allocator
|
interface Allocator
|
||||||
{
|
{
|
||||||
@nogc:
|
|
||||||
/**
|
/**
|
||||||
* Allocates $(D_PARAM s) bytes of memory.
|
* Returns: Alignment offered.
|
||||||
|
*/
|
||||||
|
@property uint alignment() const shared pure nothrow @safe @nogc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates $(D_PARAM size) bytes of memory.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* s = Amount of memory to allocate.
|
* size = Amount of memory to allocate.
|
||||||
*
|
*
|
||||||
* Returns: The pointer to the new allocated memory.
|
* Returns: Pointer to the new allocated memory.
|
||||||
*/
|
*/
|
||||||
void[] allocate(size_t s) @safe;
|
void[] allocate(in size_t size) shared nothrow @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deallocates a memory block.
|
* Deallocates a memory block.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
@ -36,7 +38,7 @@ interface Allocator
|
|||||||
*
|
*
|
||||||
* Returns: Whether the deallocation was successful.
|
* Returns: Whether the deallocation was successful.
|
||||||
*/
|
*/
|
||||||
bool deallocate(void[] p) @safe;
|
bool deallocate(void[] p) shared nothrow @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increases or decreases the size of a memory block.
|
* Increases or decreases the size of a memory block.
|
||||||
@ -45,14 +47,21 @@ interface Allocator
|
|||||||
* p = A pointer to the memory block.
|
* p = A pointer to the memory block.
|
||||||
* size = Size of the reallocated block.
|
* size = Size of the reallocated block.
|
||||||
*
|
*
|
||||||
* Returns: Whether the reallocation was successful.
|
* Returns: Pointer to the allocated memory.
|
||||||
*/
|
*/
|
||||||
bool reallocate(ref void[] p, size_t s) @safe;
|
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static allocator instance and initializer.
|
* Reallocates a memory block in place if possible or returns
|
||||||
|
* $(D_KEYWORD false). This function cannot be used to allocate or
|
||||||
|
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
|
||||||
|
* $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
|
||||||
*
|
*
|
||||||
* Returns: An $(D_PSYMBOL Allocator) instance.
|
* Params:
|
||||||
|
* p = A pointer to the memory block.
|
||||||
|
* size = Size of the reallocated block.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
|
||||||
*/
|
*/
|
||||||
static @property Allocator instance() @safe;
|
bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc;
|
||||||
}
|
}
|
||||||
|
645
source/tanya/memory/mmappool.d
Normal file
645
source/tanya/memory/mmappool.d
Normal file
@ -0,0 +1,645 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
*/
|
||||||
|
module tanya.memory.mmappool;
|
||||||
|
|
||||||
|
import core.stdc.string;
|
||||||
|
import std.algorithm.comparison;
|
||||||
|
import tanya.memory.allocator;
|
||||||
|
|
||||||
|
version (Posix)
|
||||||
|
{
|
||||||
|
import core.stdc.errno;
|
||||||
|
import core.sys.posix.sys.mman;
|
||||||
|
import core.sys.posix.unistd;
|
||||||
|
}
|
||||||
|
else version (Windows)
|
||||||
|
{
|
||||||
|
import core.sys.windows.winbase;
|
||||||
|
import core.sys.windows.windows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This allocator allocates memory in regions (multiple of 64 KB for example).
|
||||||
|
* Each region is then splitted in blocks. So it doesn't request the memory
|
||||||
|
* from the operating system on each call, but only if there are no large
|
||||||
|
* enough free blocks in the available regions.
|
||||||
|
* Deallocation works in the same way. Deallocation doesn't immediately
|
||||||
|
* gives the memory back to the operating system, but marks the appropriate
|
||||||
|
* block as free and only if all blocks in the region are free, the complete
|
||||||
|
* region is deallocated.
|
||||||
|
*
|
||||||
|
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
* | | | | | || | | |
|
||||||
|
* | |prev <----------- | || | | |
|
||||||
|
* | R | B | | B | || R | B | |
|
||||||
|
* | E | L | | L | next E | L | |
|
||||||
|
* | G | O | DATA | O | FREE ---> G | O | DATA |
|
||||||
|
* | I | C | | C | <--- I | C | |
|
||||||
|
* | O | K | | K | prev O | K | |
|
||||||
|
* | N | -----------> next| || N | | |
|
||||||
|
* | | | | | || | | |
|
||||||
|
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
*/
|
||||||
|
final class MmapPool : Allocator
|
||||||
|
{
|
||||||
|
invariant
|
||||||
|
{
|
||||||
|
for (auto r = &head; *r !is null; r = &((*r).next))
|
||||||
|
{
|
||||||
|
auto block = cast(Block) (cast(void*) *r + RegionEntry.sizeof);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
assert(block.prev is null || block.prev.next is block);
|
||||||
|
assert(block.next is null || block.next.prev is block);
|
||||||
|
assert(block.region is *r);
|
||||||
|
}
|
||||||
|
while ((block = block.next) !is null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates $(D_PARAM size) bytes of memory.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* size = Amount of memory to allocate.
|
||||||
|
*
|
||||||
|
* Returns: Pointer to the new allocated memory.
|
||||||
|
*/
|
||||||
|
void[] allocate(in size_t size) shared nothrow @nogc
|
||||||
|
{
|
||||||
|
if (!size)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
immutable dataSize = addAlignment(size);
|
||||||
|
|
||||||
|
void* data = findBlock(dataSize);
|
||||||
|
if (data is null)
|
||||||
|
{
|
||||||
|
data = initializeRegion(dataSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data is null ? null : data[0 .. size];
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
nothrow unittest
|
||||||
|
{
|
||||||
|
auto p = MmapPool.instance.allocate(20);
|
||||||
|
|
||||||
|
assert(p);
|
||||||
|
|
||||||
|
MmapPool.instance.deallocate(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a block large enough to keep $(D_PARAM size) and split it
|
||||||
|
* into two blocks if the block is too large.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* size = Minimum size the block should have (aligned).
|
||||||
|
*
|
||||||
|
* Returns: Data the block points to or $(D_KEYWORD null).
|
||||||
|
*/
|
||||||
|
private void* findBlock(in ref size_t size) shared nothrow @nogc
|
||||||
|
{
|
||||||
|
Block block1;
|
||||||
|
RegionLoop: for (auto r = head; r !is null; r = r.next)
|
||||||
|
{
|
||||||
|
block1 = cast(Block) (cast(void*) r + RegionEntry.sizeof);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (block1.free && block1.size >= size)
|
||||||
|
{
|
||||||
|
break RegionLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while ((block1 = block1.next) !is null);
|
||||||
|
}
|
||||||
|
if (block1 is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (block1.size >= size + alignment_ + BlockEntry.sizeof)
|
||||||
|
{ // Split the block if needed
|
||||||
|
Block block2 = cast(Block) (cast(void*) block1 + BlockEntry.sizeof + size);
|
||||||
|
block2.prev = block1;
|
||||||
|
block2.next = block1.next;
|
||||||
|
block2.free = true;
|
||||||
|
block2.size = block1.size - BlockEntry.sizeof - size;
|
||||||
|
block2.region = block1.region;
|
||||||
|
|
||||||
|
if (block1.next !is null)
|
||||||
|
{
|
||||||
|
block1.next.prev = block2;
|
||||||
|
}
|
||||||
|
block1.next = block2;
|
||||||
|
block1.size = size;
|
||||||
|
}
|
||||||
|
block1.free = false;
|
||||||
|
block1.region.blocks = block1.region.blocks + 1;
|
||||||
|
|
||||||
|
return cast(void*) block1 + BlockEntry.sizeof;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge block with the next one.
|
||||||
|
private void mergeNext(Block block) shared const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
block.size = block.size + BlockEntry.sizeof + block.next.size;
|
||||||
|
if (block.next.next !is null)
|
||||||
|
{
|
||||||
|
block.next.next.prev = block;
|
||||||
|
}
|
||||||
|
block.next = block.next.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deallocates a memory block.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* p = A pointer to the memory block to be freed.
|
||||||
|
*
|
||||||
|
* Returns: Whether the deallocation was successful.
|
||||||
|
*/
|
||||||
|
bool deallocate(void[] p) shared nothrow @nogc
|
||||||
|
{
|
||||||
|
if (p.ptr is null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Block block = cast(Block) (p.ptr - BlockEntry.sizeof);
|
||||||
|
if (block.region.blocks <= 1)
|
||||||
|
{
|
||||||
|
if (block.region.prev !is null)
|
||||||
|
{
|
||||||
|
block.region.prev.next = block.region.next;
|
||||||
|
}
|
||||||
|
else // Replace the list head. It is being deallocated
|
||||||
|
{
|
||||||
|
head = block.region.next;
|
||||||
|
}
|
||||||
|
if (block.region.next !is null)
|
||||||
|
{
|
||||||
|
block.region.next.prev = block.region.prev;
|
||||||
|
}
|
||||||
|
version (Posix)
|
||||||
|
{
|
||||||
|
return munmap(cast(void*) block.region, block.region.size) == 0;
|
||||||
|
}
|
||||||
|
version (Windows)
|
||||||
|
{
|
||||||
|
return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Merge blocks if neigbours are free.
|
||||||
|
if (block.next !is null && block.next.free)
|
||||||
|
{
|
||||||
|
mergeNext(block);
|
||||||
|
}
|
||||||
|
if (block.prev !is null && block.prev.free)
|
||||||
|
{
|
||||||
|
block.prev.size = block.prev.size + BlockEntry.sizeof + block.size;
|
||||||
|
if (block.next !is null)
|
||||||
|
{
|
||||||
|
block.next.prev = block.prev;
|
||||||
|
}
|
||||||
|
block.prev.next = block.next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
block.free = true;
|
||||||
|
}
|
||||||
|
block.region.blocks = block.region.blocks - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
nothrow unittest
|
||||||
|
{
|
||||||
|
auto p = MmapPool.instance.allocate(20);
|
||||||
|
|
||||||
|
assert(MmapPool.instance.deallocate(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reallocates a memory block in place if possible or returns
|
||||||
|
* $(D_KEYWORD false). This function cannot be used to allocate or
|
||||||
|
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
|
||||||
|
* $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* p = A pointer to the memory block.
|
||||||
|
* size = Size of the reallocated block.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
|
bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc
|
||||||
|
{
|
||||||
|
if (p is null || size == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (size <= p.length)
|
||||||
|
{
|
||||||
|
// Leave the block as is.
|
||||||
|
p = p.ptr[0 .. size];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Block block1 = cast(Block) (p.ptr - BlockEntry.sizeof);
|
||||||
|
|
||||||
|
if (block1.size >= size)
|
||||||
|
{
|
||||||
|
// Enough space in the current block. Can happen because of the alignment.
|
||||||
|
p = p.ptr[0 .. size];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
immutable dataSize = addAlignment(size);
|
||||||
|
immutable delta = dataSize - addAlignment(p.length);
|
||||||
|
|
||||||
|
if (block1.next is null
|
||||||
|
|| !block1.next.free
|
||||||
|
|| block1.next.size + BlockEntry.sizeof < delta)
|
||||||
|
{
|
||||||
|
// It is the last block in the region or the next block is too small or not
|
||||||
|
// free.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (block1.next.size >= delta + alignment_)
|
||||||
|
{
|
||||||
|
// Move size from block2 to block1.
|
||||||
|
block1.next.size = block1.next.size - delta;
|
||||||
|
block1.size = block1.size + delta;
|
||||||
|
|
||||||
|
auto block2 = cast(Block) (p.ptr + dataSize);
|
||||||
|
if (block1.next.next !is null)
|
||||||
|
{
|
||||||
|
block1.next.next.prev = block2;
|
||||||
|
}
|
||||||
|
// block1.next and block2 can overlap.
|
||||||
|
memmove(cast(void*) block2, cast(void*) block1.next, BlockEntry.sizeof);
|
||||||
|
block1.next = block2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The next block has enough space, but is too small for further
|
||||||
|
// allocations. Merge it with the current block.
|
||||||
|
mergeNext(block1);
|
||||||
|
}
|
||||||
|
|
||||||
|
p = p.ptr[0 .. size];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
nothrow unittest
|
||||||
|
{
|
||||||
|
void[] p;
|
||||||
|
assert(!MmapPool.instance.reallocateInPlace(p, 5));
|
||||||
|
assert(p is null);
|
||||||
|
|
||||||
|
p = MmapPool.instance.allocate(1);
|
||||||
|
auto orig = p.ptr;
|
||||||
|
|
||||||
|
assert(MmapPool.instance.reallocateInPlace(p, 2));
|
||||||
|
assert(p.length == 2);
|
||||||
|
assert(p.ptr == orig);
|
||||||
|
|
||||||
|
assert(MmapPool.instance.reallocateInPlace(p, 4));
|
||||||
|
assert(p.length == 4);
|
||||||
|
assert(p.ptr == orig);
|
||||||
|
|
||||||
|
assert(MmapPool.instance.reallocateInPlace(p, 2));
|
||||||
|
assert(p.length == 2);
|
||||||
|
assert(p.ptr == orig);
|
||||||
|
|
||||||
|
MmapPool.instance.deallocate(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases or decreases the size of a memory block.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* p = A pointer to the memory block.
|
||||||
|
* size = Size of the reallocated block.
|
||||||
|
*
|
||||||
|
* Returns: Whether the reallocation was successful.
|
||||||
|
*/
|
||||||
|
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
if (deallocate(p))
|
||||||
|
{
|
||||||
|
p = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (reallocateInPlace(p, size))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Can't reallocate in place, allocate a new block,
|
||||||
|
// copy and delete the previous one.
|
||||||
|
void[] reallocP = allocate(size);
|
||||||
|
if (reallocP is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (p !is null)
|
||||||
|
{
|
||||||
|
memcpy(reallocP.ptr, p.ptr, min(p.length, size));
|
||||||
|
deallocate(p);
|
||||||
|
}
|
||||||
|
p = reallocP;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
nothrow unittest
|
||||||
|
{
|
||||||
|
void[] p;
|
||||||
|
MmapPool.instance.reallocate(p, 10 * int.sizeof);
|
||||||
|
(cast(int[]) p)[7] = 123;
|
||||||
|
|
||||||
|
assert(p.length == 40);
|
||||||
|
|
||||||
|
MmapPool.instance.reallocate(p, 8 * int.sizeof);
|
||||||
|
|
||||||
|
assert(p.length == 32);
|
||||||
|
assert((cast(int[]) p)[7] == 123);
|
||||||
|
|
||||||
|
MmapPool.instance.reallocate(p, 20 * int.sizeof);
|
||||||
|
(cast(int[]) p)[15] = 8;
|
||||||
|
|
||||||
|
assert(p.length == 80);
|
||||||
|
assert((cast(int[]) p)[15] == 8);
|
||||||
|
assert((cast(int[]) p)[7] == 123);
|
||||||
|
|
||||||
|
MmapPool.instance.reallocate(p, 8 * int.sizeof);
|
||||||
|
|
||||||
|
assert(p.length == 32);
|
||||||
|
assert((cast(int[]) p)[7] == 123);
|
||||||
|
|
||||||
|
MmapPool.instance.deallocate(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static allocator instance and initializer.
|
||||||
|
*
|
||||||
|
* Returns: Global $(D_PSYMBOL MmapPool) instance.
|
||||||
|
*/
|
||||||
|
static @property ref shared(MmapPool) instance() nothrow @nogc
|
||||||
|
{
|
||||||
|
if (instance_ is null)
|
||||||
|
{
|
||||||
|
// Get system dependend page size.
|
||||||
|
version (Posix)
|
||||||
|
{
|
||||||
|
pageSize = sysconf(_SC_PAGE_SIZE);
|
||||||
|
if (pageSize < 65536)
|
||||||
|
{
|
||||||
|
pageSize = pageSize * 65536 / pageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else version (Windows)
|
||||||
|
{
|
||||||
|
SYSTEM_INFO si;
|
||||||
|
GetSystemInfo(&si);
|
||||||
|
pageSize = si.dwPageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool));
|
||||||
|
|
||||||
|
Region head; // Will become soon our region list head
|
||||||
|
void* data = initializeRegion(instanceSize, head);
|
||||||
|
if (data !is null)
|
||||||
|
{
|
||||||
|
memcpy(data, typeid(MmapPool).initializer.ptr, instanceSize);
|
||||||
|
instance_ = cast(shared MmapPool) data;
|
||||||
|
instance_.head = head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
nothrow unittest
|
||||||
|
{
|
||||||
|
assert(instance is instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initializes a region for one element.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* size = Aligned size of the first data block in the region.
|
||||||
|
* head = Region list head.
|
||||||
|
*
|
||||||
|
* Returns: A pointer to the data.
|
||||||
|
*/
|
||||||
|
private static void* initializeRegion(size_t size, ref Region head)
|
||||||
|
nothrow @nogc
|
||||||
|
{
|
||||||
|
immutable regionSize = calculateRegionSize(size);
|
||||||
|
|
||||||
|
version (Posix)
|
||||||
|
{
|
||||||
|
void* p = mmap(null,
|
||||||
|
regionSize,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANON,
|
||||||
|
-1,
|
||||||
|
0);
|
||||||
|
if (p is MAP_FAILED)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else version (Windows)
|
||||||
|
{
|
||||||
|
void* p = VirtualAlloc(null,
|
||||||
|
regionSize,
|
||||||
|
MEM_COMMIT,
|
||||||
|
PAGE_READWRITE);
|
||||||
|
if (p is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Region region = cast(Region) p;
|
||||||
|
region.blocks = 1;
|
||||||
|
region.size = regionSize;
|
||||||
|
|
||||||
|
// Set the pointer to the head of the region list
|
||||||
|
if (head !is null)
|
||||||
|
{
|
||||||
|
head.prev = region;
|
||||||
|
}
|
||||||
|
region.next = head;
|
||||||
|
region.prev = null;
|
||||||
|
head = region;
|
||||||
|
|
||||||
|
// Initialize the data block
|
||||||
|
void* memoryPointer = p + RegionEntry.sizeof;
|
||||||
|
Block block1 = cast(Block) memoryPointer;
|
||||||
|
block1.size = size;
|
||||||
|
block1.free = false;
|
||||||
|
|
||||||
|
// It is what we want to return
|
||||||
|
void* data = memoryPointer + BlockEntry.sizeof;
|
||||||
|
|
||||||
|
// Free block after data
|
||||||
|
memoryPointer = data + size;
|
||||||
|
Block block2 = cast(Block) memoryPointer;
|
||||||
|
block1.prev = block2.next = null;
|
||||||
|
block1.next = block2;
|
||||||
|
block2.prev = block1;
|
||||||
|
block2.size = regionSize - size - RegionEntry.sizeof - BlockEntry.sizeof * 2;
|
||||||
|
block2.free = true;
|
||||||
|
block1.region = block2.region = region;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void* initializeRegion(size_t size) shared nothrow @nogc
|
||||||
|
{
|
||||||
|
return initializeRegion(size, head);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Params:
|
||||||
|
* x = Space to be aligned.
|
||||||
|
*
|
||||||
|
* Returns: Aligned size of $(D_PARAM x).
|
||||||
|
*/
|
||||||
|
private static immutable(size_t) addAlignment(size_t x)
|
||||||
|
pure nothrow @safe @nogc
|
||||||
|
out (result)
|
||||||
|
{
|
||||||
|
assert(result > 0);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return (x - 1) / alignment_ * alignment_ + alignment_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Params:
|
||||||
|
* x = Required space.
|
||||||
|
*
|
||||||
|
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
|
||||||
|
*/
|
||||||
|
private static immutable(size_t) calculateRegionSize(size_t x)
|
||||||
|
nothrow @safe @nogc
|
||||||
|
out (result)
|
||||||
|
{
|
||||||
|
assert(result > 0);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
x += RegionEntry.sizeof + BlockEntry.sizeof * 2;
|
||||||
|
return x / pageSize * pageSize + pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Alignment offered.
|
||||||
|
*/
|
||||||
|
@property uint alignment() shared const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return alignment_;
|
||||||
|
}
|
||||||
|
private enum alignment_ = 8;
|
||||||
|
|
||||||
|
private shared static MmapPool instance_;
|
||||||
|
private shared static size_t pageSize;
|
||||||
|
|
||||||
|
private shared struct RegionEntry
|
||||||
|
{
|
||||||
|
Region prev;
|
||||||
|
Region next;
|
||||||
|
uint blocks;
|
||||||
|
size_t size;
|
||||||
|
}
|
||||||
|
private alias Region = shared RegionEntry*;
|
||||||
|
private shared Region head;
|
||||||
|
|
||||||
|
private shared struct BlockEntry
|
||||||
|
{
|
||||||
|
Block prev;
|
||||||
|
Block next;
|
||||||
|
Region region;
|
||||||
|
size_t size;
|
||||||
|
bool free;
|
||||||
|
}
|
||||||
|
private alias Block = shared BlockEntry*;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A lot of allocations/deallocations, but it is the minimum caused a
|
||||||
|
// segmentation fault because MmapPool reallocateInPlace moves a block wrong.
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto a = MmapPool.instance.allocate(16);
|
||||||
|
auto d = MmapPool.instance.allocate(16);
|
||||||
|
auto b = MmapPool.instance.allocate(16);
|
||||||
|
auto e = MmapPool.instance.allocate(16);
|
||||||
|
auto c = MmapPool.instance.allocate(16);
|
||||||
|
auto f = MmapPool.instance.allocate(16);
|
||||||
|
|
||||||
|
MmapPool.instance.deallocate(a);
|
||||||
|
MmapPool.instance.deallocate(b);
|
||||||
|
MmapPool.instance.deallocate(c);
|
||||||
|
|
||||||
|
a = MmapPool.instance.allocate(50);
|
||||||
|
MmapPool.instance.reallocateInPlace(a, 64);
|
||||||
|
MmapPool.instance.deallocate(a);
|
||||||
|
|
||||||
|
a = MmapPool.instance.allocate(1);
|
||||||
|
auto tmp1 = MmapPool.instance.allocate(1);
|
||||||
|
auto h1 = MmapPool.instance.allocate(1);
|
||||||
|
auto tmp2 = cast(ubyte[]) MmapPool.instance.allocate(1);
|
||||||
|
|
||||||
|
auto h2 = MmapPool.instance.allocate(2);
|
||||||
|
tmp1 = MmapPool.instance.allocate(1);
|
||||||
|
MmapPool.instance.deallocate(h2);
|
||||||
|
MmapPool.instance.deallocate(h1);
|
||||||
|
|
||||||
|
h2 = MmapPool.instance.allocate(2);
|
||||||
|
h1 = MmapPool.instance.allocate(1);
|
||||||
|
MmapPool.instance.deallocate(h2);
|
||||||
|
|
||||||
|
auto rep = cast(void[]) tmp2;
|
||||||
|
MmapPool.instance.reallocate(rep, tmp1.length);
|
||||||
|
tmp2 = cast(ubyte[]) rep;
|
||||||
|
|
||||||
|
MmapPool.instance.reallocate(tmp1, 9);
|
||||||
|
|
||||||
|
rep = cast(void[]) tmp2;
|
||||||
|
MmapPool.instance.reallocate(rep, tmp1.length);
|
||||||
|
tmp2 = cast(ubyte[]) rep;
|
||||||
|
MmapPool.instance.reallocate(tmp1, 17);
|
||||||
|
|
||||||
|
tmp2[$ - 1] = 0;
|
||||||
|
|
||||||
|
MmapPool.instance.deallocate(tmp1);
|
||||||
|
|
||||||
|
b = MmapPool.instance.allocate(16);
|
||||||
|
|
||||||
|
MmapPool.instance.deallocate(h1);
|
||||||
|
MmapPool.instance.deallocate(a);
|
||||||
|
MmapPool.instance.deallocate(b);
|
||||||
|
MmapPool.instance.deallocate(d);
|
||||||
|
MmapPool.instance.deallocate(e);
|
||||||
|
MmapPool.instance.deallocate(f);
|
||||||
|
}
|
@ -3,205 +3,315 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.memory;
|
module tanya.memory;
|
||||||
|
|
||||||
public import tanya.memory.allocator;
|
import core.exception;
|
||||||
public import std.experimental.allocator : make, makeArray, expandArray, shrinkArray, IAllocator;
|
public import std.experimental.allocator : make, makeArray;
|
||||||
import core.atomic;
|
|
||||||
import core.stdc.stdlib;
|
|
||||||
import std.traits;
|
import std.traits;
|
||||||
|
public import tanya.memory.allocator;
|
||||||
|
|
||||||
version (Windows)
|
/**
|
||||||
|
* The mixin generates common methods for classes and structs using
|
||||||
|
* allocators. It provides a protected member, constructor and a read-only property,
|
||||||
|
* that checks if an allocator was already set and sets it to the default
|
||||||
|
* one, if not (useful for structs which don't have a default constructor).
|
||||||
|
*/
|
||||||
|
mixin template DefaultAllocator()
|
||||||
{
|
{
|
||||||
import core.sys.windows.windows;
|
/// Allocator.
|
||||||
}
|
protected shared Allocator allocator_;
|
||||||
else version (Posix)
|
|
||||||
{
|
|
||||||
public import tanya.memory.ullocator;
|
|
||||||
import core.sys.posix.pthread;
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc:
|
/**
|
||||||
|
* Params:
|
||||||
version (Windows)
|
* allocator = The allocator should be used.
|
||||||
{
|
*/
|
||||||
package alias Mutex = CRITICAL_SECTION;
|
this(shared Allocator allocator)
|
||||||
package alias destroyMutex = DeleteCriticalSection;
|
in
|
||||||
}
|
|
||||||
else version (Posix)
|
|
||||||
{
|
|
||||||
package alias Mutex = pthread_mutex_t;
|
|
||||||
package void destroyMutex(pthread_mutex_t* mtx)
|
|
||||||
{
|
|
||||||
pthread_mutex_destroy(mtx) && assert(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@property void defaultAllocator(Allocator allocator) @safe nothrow
|
|
||||||
{
|
|
||||||
_defaultAllocator = allocator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@property Allocator defaultAllocator() @safe nothrow
|
|
||||||
{
|
|
||||||
return _defaultAllocator;
|
|
||||||
}
|
|
||||||
|
|
||||||
static this() @safe nothrow
|
|
||||||
{
|
|
||||||
defaultAllocator = Ullocator.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
package struct Monitor
|
|
||||||
{
|
|
||||||
Object.Monitor impl; // for user-level monitors
|
|
||||||
void delegate(Object) @nogc[] devt; // for internal monitors
|
|
||||||
size_t refs; // reference count
|
|
||||||
version (Posix)
|
|
||||||
{
|
{
|
||||||
Mutex mtx;
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this.allocator_ = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This property checks if the allocator was set in the constructor
|
||||||
|
* and sets it to the default one, if not.
|
||||||
|
*
|
||||||
|
* Returns: Used allocator.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE allocator_ !is null)
|
||||||
|
*/
|
||||||
|
protected @property shared(Allocator) allocator() nothrow @safe @nogc
|
||||||
|
out (allocator)
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
if (allocator_ is null)
|
||||||
|
{
|
||||||
|
allocator_ = defaultAllocator;
|
||||||
|
}
|
||||||
|
return allocator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
@property shared(Allocator) allocator() const nothrow @trusted @nogc
|
||||||
|
out (allocator)
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
if (allocator_ is null)
|
||||||
|
{
|
||||||
|
return defaultAllocator;
|
||||||
|
}
|
||||||
|
return cast(shared Allocator) allocator_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
package @property ref shared(Monitor*) monitor(Object h) pure nothrow
|
// From druntime
|
||||||
|
private extern (C) void _d_monitordelete(Object h, bool det) nothrow @nogc;
|
||||||
|
|
||||||
|
shared Allocator allocator;
|
||||||
|
|
||||||
|
shared static this() nothrow @trusted @nogc
|
||||||
{
|
{
|
||||||
return *cast(shared Monitor**)&h.__monitor;
|
import tanya.memory.mmappool;
|
||||||
|
allocator = MmapPool.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc
|
||||||
|
out (allocator)
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
.allocator = allocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroys and then deallocates (using $(D_PARAM allocator)) the class
|
* Returns the size in bytes of the state that needs to be allocated to hold an
|
||||||
* object referred to by a $(D_KEYWORD class) or $(D_KEYWORD interface)
|
* object of type $(D_PARAM T).
|
||||||
* reference. It is assumed the respective entities had been allocated with
|
|
||||||
* the same allocator.
|
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* A = The type of the allocator used for the ojbect allocation.
|
* T = Object type.
|
||||||
* T = The type of the object that should be destroyed.
|
|
||||||
* allocator = The allocator used for the object allocation.
|
|
||||||
* p = The object should be destroyed.
|
|
||||||
*/
|
*/
|
||||||
void finalize(A, T)(auto ref A allocator, ref T p)
|
template stateSize(T)
|
||||||
|
{
|
||||||
|
static if (is(T == class) || is(T == interface))
|
||||||
|
{
|
||||||
|
enum stateSize = __traits(classInstanceSize, T);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
enum stateSize = T.sizeof;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* size = Raw size.
|
||||||
|
* alignment = Alignment.
|
||||||
|
*
|
||||||
|
* Returns: Aligned size.
|
||||||
|
*/
|
||||||
|
size_t alignedSize(in size_t size, in size_t alignment = 8) pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return (size - 1) / alignment * alignment + alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal function used to create, resize or destroy a dynamic array. It
|
||||||
|
* throws $(D_PSYMBOL OutOfMemoryError) if $(D_PARAM Throws) is set. The new
|
||||||
|
* allocated part of the array is initialized only if $(D_PARAM Init)
|
||||||
|
* is set. This function can be trusted only in the data structures that
|
||||||
|
* can ensure that the array is allocated/rellocated/deallocated with the
|
||||||
|
* same allocator.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Element type of the array being created.
|
||||||
|
* Init = If should be initialized.
|
||||||
|
* Throws = If $(D_PSYMBOL OutOfMemoryError) should be throwsn.
|
||||||
|
* allocator = The allocator used for getting memory.
|
||||||
|
* array = A reference to the array being changed.
|
||||||
|
* length = New array length.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
|
||||||
|
* not be reallocated. In the latter
|
||||||
|
*/
|
||||||
|
package(tanya) bool resize(T,
|
||||||
|
bool Init = true,
|
||||||
|
bool Throws = true)
|
||||||
|
(shared Allocator allocator,
|
||||||
|
ref T[] array,
|
||||||
|
in size_t length) @trusted
|
||||||
|
{
|
||||||
|
void[] buf = array;
|
||||||
|
static if (Init)
|
||||||
|
{
|
||||||
|
immutable oldLength = array.length;
|
||||||
|
}
|
||||||
|
if (!allocator.reallocate(buf, length * T.sizeof))
|
||||||
|
{
|
||||||
|
static if (Throws)
|
||||||
|
{
|
||||||
|
onOutOfMemoryError;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Casting from void[] is unsafe, but we know we cast to the original type.
|
||||||
|
array = cast(T[]) buf;
|
||||||
|
|
||||||
|
static if (Init)
|
||||||
|
{
|
||||||
|
if (oldLength < length)
|
||||||
|
{
|
||||||
|
array[oldLength .. $] = T.init;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
package(tanya) alias resizeArray = resize;
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
int[] p;
|
||||||
|
|
||||||
|
defaultAllocator.resizeArray(p, 20);
|
||||||
|
assert(p.length == 20);
|
||||||
|
|
||||||
|
defaultAllocator.resizeArray(p, 30);
|
||||||
|
assert(p.length == 30);
|
||||||
|
|
||||||
|
defaultAllocator.resizeArray(p, 10);
|
||||||
|
assert(p.length == 10);
|
||||||
|
|
||||||
|
defaultAllocator.resizeArray(p, 0);
|
||||||
|
assert(p is null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T).
|
||||||
|
* It is assumed the respective entities had been allocated with the same
|
||||||
|
* allocator.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Type of $(D_PARAM p).
|
||||||
|
* allocator = Allocator the $(D_PARAM p) was allocated with.
|
||||||
|
* p = Object or array to be destroyed.
|
||||||
|
*/
|
||||||
|
void dispose(T)(shared Allocator allocator, auto ref T* p)
|
||||||
|
{
|
||||||
|
static if (hasElaborateDestructor!T)
|
||||||
|
{
|
||||||
|
destroy(*p);
|
||||||
|
}
|
||||||
|
() @trusted { allocator.deallocate((cast(void*) p)[0 .. T.sizeof]); }();
|
||||||
|
p = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
void dispose(T)(shared Allocator allocator, auto ref T p)
|
||||||
if (is(T == class) || is(T == interface))
|
if (is(T == class) || is(T == interface))
|
||||||
{
|
{
|
||||||
|
if (p is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
static if (is(T == interface))
|
static if (is(T == interface))
|
||||||
{
|
{
|
||||||
|
version(Windows)
|
||||||
|
{
|
||||||
|
import core.sys.windows.unknwn : IUnknown;
|
||||||
|
static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in "
|
||||||
|
~ __PRETTY_FUNCTION__);
|
||||||
|
}
|
||||||
auto ob = cast(Object) p;
|
auto ob = cast(Object) p;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
alias ob = p;
|
alias ob = p;
|
||||||
}
|
}
|
||||||
auto pp = cast(void*) ob;
|
auto ptr = cast(void *) ob;
|
||||||
auto ppv = cast(void**) pp;
|
|
||||||
if (!pp || !*ppv)
|
auto support = ptr[0 .. typeid(ob).initializer.length];
|
||||||
|
scope (success)
|
||||||
|
{
|
||||||
|
() @trusted { allocator.deallocate(support); }();
|
||||||
|
p = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ppv = cast(void**) ptr;
|
||||||
|
if (!*ppv)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto support = (cast(void*) ob)[0 .. typeid(ob).initializer.length];
|
|
||||||
auto pc = cast(ClassInfo*) *ppv;
|
auto pc = cast(ClassInfo*) *ppv;
|
||||||
|
scope (exit)
|
||||||
|
{
|
||||||
|
*ppv = null;
|
||||||
|
}
|
||||||
|
|
||||||
auto c = *pc;
|
auto c = *pc;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
// Assume the destructor is @nogc. Leave it nothrow since the destructor
|
||||||
|
// shouldn't throw and if it does, it is an error anyway.
|
||||||
if (c.destructor)
|
if (c.destructor)
|
||||||
{
|
{
|
||||||
(cast(void function(Object)) c.destructor)(ob);
|
(cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
|
||||||
}
|
|
||||||
} while ((c = c.base) !is null);
|
|
||||||
|
|
||||||
// Take care of monitors for synchronized blocks
|
|
||||||
if (ppv[1])
|
|
||||||
{
|
|
||||||
shared(Monitor)* m = atomicLoad!(MemoryOrder.acq)(ob.monitor);
|
|
||||||
if (m !is null)
|
|
||||||
{
|
|
||||||
auto mc = cast(Monitor*) m;
|
|
||||||
if (!atomicOp!("-=")(m.refs, cast(size_t) 1))
|
|
||||||
{
|
|
||||||
foreach (v; mc.devt)
|
|
||||||
{
|
|
||||||
if (v)
|
|
||||||
{
|
|
||||||
v(ob);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mc.devt.ptr)
|
|
||||||
{
|
|
||||||
free(mc.devt.ptr);
|
|
||||||
}
|
|
||||||
destroyMutex(&mc.mtx);
|
|
||||||
free(mc);
|
|
||||||
atomicStore!(MemoryOrder.rel)(ob.monitor, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*ppv = null;
|
while ((c = c.base) !is null);
|
||||||
|
|
||||||
allocator.deallocate(support);
|
if (ppv[1]) // if monitor is not null
|
||||||
p = null;
|
{
|
||||||
|
_d_monitordelete(cast(Object) ptr, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
void finalize(A, T)(auto ref A allocator, ref T *p)
|
void dispose(T)(shared Allocator allocator, auto ref T[] p)
|
||||||
if (is(T == struct))
|
|
||||||
{
|
{
|
||||||
if (p is null)
|
static if (hasElaborateDestructor!(typeof(p[0])))
|
||||||
{
|
{
|
||||||
return;
|
import std.algorithm.iteration;
|
||||||
|
p.each!(e => destroy(e));
|
||||||
}
|
}
|
||||||
static if (hasElaborateDestructor!T)
|
() @trusted { allocator.deallocate(p); }();
|
||||||
{
|
|
||||||
*p.__xdtor();
|
|
||||||
}
|
|
||||||
allocator.deallocate((cast(void*)p)[0 .. T.sizeof]);
|
|
||||||
p = null;
|
p = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
unittest
|
||||||
void finalize(A, T)(auto ref A allocator, ref T[] p)
|
|
||||||
{
|
{
|
||||||
static if (hasElaborateDestructor!T)
|
struct S
|
||||||
{
|
{
|
||||||
foreach (ref e; p)
|
~this()
|
||||||
{
|
{
|
||||||
finalize(allocator, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allocator.deallocate(p);
|
auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
|
||||||
p = null;
|
|
||||||
|
defaultAllocator.dispose(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool resizeArray(T, A)(auto ref A allocator, ref T[] array, in size_t length)
|
|
||||||
@trusted
|
|
||||||
{
|
|
||||||
if (length == array.length)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (array is null && length > 0)
|
|
||||||
{
|
|
||||||
array = makeArray!T(allocator, length);
|
|
||||||
return array !is null;
|
|
||||||
}
|
|
||||||
if (length == 0)
|
|
||||||
{
|
|
||||||
finalize(allocator, array);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
void[] buf = array;
|
|
||||||
if (!allocator.reallocate(buf, length * T.sizeof))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
array = cast(T[]) buf;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum bool isFinalizable(T) = is(T == class) || is(T == interface)
|
|
||||||
|| hasElaborateDestructor!T || isDynamicArray!T;
|
|
||||||
|
|
||||||
private Allocator _defaultAllocator;
|
|
||||||
|
458
source/tanya/memory/types.d
Normal file
458
source/tanya/memory/types.d
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright: Eugene Wissner 2016.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||||
|
*/
|
||||||
|
module tanya.memory.types;
|
||||||
|
|
||||||
|
import core.exception;
|
||||||
|
import std.algorithm.comparison;
|
||||||
|
import std.algorithm.mutation;
|
||||||
|
import std.conv;
|
||||||
|
import std.traits;
|
||||||
|
import tanya.memory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference-counted object containing a $(D_PARAM T) value as payload.
|
||||||
|
* $(D_PSYMBOL RefCounted) keeps track of all references of an object, and
|
||||||
|
* when the reference count goes down to zero, frees the underlying store.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Type of the reference-counted value.
|
||||||
|
*/
|
||||||
|
struct RefCounted(T)
|
||||||
|
{
|
||||||
|
static if (is(T == class) || is(T == interface))
|
||||||
|
{
|
||||||
|
private alias Payload = T;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
private alias Payload = T*;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Storage
|
||||||
|
{
|
||||||
|
private Payload payload;
|
||||||
|
private size_t counter = 1;
|
||||||
|
|
||||||
|
private final size_t opUnary(string op)() pure nothrow @safe @nogc
|
||||||
|
if (op == "--" || op == "++")
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(counter > 0);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
mixin("return " ~ op ~ "counter;");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int opCmp(size_t counter) const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
if (this.counter > counter)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (this.counter < counter)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int opEquals(size_t counter) const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return this.counter == counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class RefCountedStorage : Storage
|
||||||
|
{
|
||||||
|
private shared Allocator allocator;
|
||||||
|
|
||||||
|
this(shared Allocator allocator) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this.allocator = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
~this() nothrow @nogc
|
||||||
|
{
|
||||||
|
allocator.dispose(payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Storage storage;
|
||||||
|
|
||||||
|
invariant
|
||||||
|
{
|
||||||
|
assert(storage is null || allocator_ !is null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes ownership over $(D_PARAM value), setting the counter to 1.
|
||||||
|
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* value = Value whose ownership is taken over.
|
||||||
|
* allocator = Allocator used to destroy the $(D_PARAM value) and to
|
||||||
|
* allocate/deallocate internal storage.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE allocator !is null)
|
||||||
|
*/
|
||||||
|
this(Payload value, shared Allocator allocator = defaultAllocator)
|
||||||
|
{
|
||||||
|
this(allocator);
|
||||||
|
storage = allocator.make!RefCountedStorage(allocator);
|
||||||
|
move(value, storage.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
this(shared Allocator allocator)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this.allocator_ = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases the reference counter by one.
|
||||||
|
*/
|
||||||
|
this(this)
|
||||||
|
{
|
||||||
|
if (count != 0)
|
||||||
|
{
|
||||||
|
++storage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decreases the reference counter by one.
|
||||||
|
*
|
||||||
|
* If the counter reaches 0, destroys the owned object.
|
||||||
|
*/
|
||||||
|
~this()
|
||||||
|
{
|
||||||
|
if (storage !is null && !(storage.counter && --storage))
|
||||||
|
{
|
||||||
|
allocator_.dispose(storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes ownership over $(D_PARAM rhs). Initializes this
|
||||||
|
* $(D_PSYMBOL RefCounted) if needed.
|
||||||
|
*
|
||||||
|
* If it is the last reference of the previously owned object,
|
||||||
|
* it will be destroyed.
|
||||||
|
*
|
||||||
|
* To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
|
||||||
|
*
|
||||||
|
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
|
||||||
|
* be used. If you need a different allocator, create a new
|
||||||
|
* $(D_PSYMBOL RefCounted) and assign it.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* rhs = $(D_KEYWORD this).
|
||||||
|
*/
|
||||||
|
ref typeof(this) opAssign(Payload rhs)
|
||||||
|
{
|
||||||
|
if (storage is null)
|
||||||
|
{
|
||||||
|
storage = allocator.make!RefCountedStorage(allocator);
|
||||||
|
}
|
||||||
|
else if (storage > 1)
|
||||||
|
{
|
||||||
|
--storage;
|
||||||
|
storage = allocator.make!RefCountedStorage(allocator);
|
||||||
|
}
|
||||||
|
else if (cast(RefCountedStorage) storage is null)
|
||||||
|
{
|
||||||
|
// Created with refCounted. Always destroyed togethter with the pointer.
|
||||||
|
assert(storage.counter != 0);
|
||||||
|
allocator.dispose(storage);
|
||||||
|
storage = allocator.make!RefCountedStorage(allocator);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allocator.dispose(storage.payload);
|
||||||
|
}
|
||||||
|
move(rhs, storage.payload);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
ref typeof(this) opAssign(typeof(null))
|
||||||
|
{
|
||||||
|
if (storage is null)
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
else if (storage > 1)
|
||||||
|
{
|
||||||
|
--storage;
|
||||||
|
storage = null;
|
||||||
|
}
|
||||||
|
else if (cast(RefCountedStorage) storage is null)
|
||||||
|
{
|
||||||
|
// Created with refCounted. Always destroyed togethter with the pointer.
|
||||||
|
assert(storage.counter != 0);
|
||||||
|
allocator.dispose(storage);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allocator.dispose(storage.payload);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
ref typeof(this) opAssign(typeof(this) rhs)
|
||||||
|
{
|
||||||
|
swap(allocator_, rhs.allocator_);
|
||||||
|
swap(storage, rhs.storage);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Reference to the owned object.
|
||||||
|
*/
|
||||||
|
inout(Payload) get() inout pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(count > 0, "Attempted to access an uninitialized reference.");
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return storage.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
static if (isPointer!Payload)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* op = Operation.
|
||||||
|
*
|
||||||
|
* Dereferences the pointer. It is defined only for pointers, not for
|
||||||
|
* reference types like classes, that can be accessed directly.
|
||||||
|
*
|
||||||
|
* Returns: Reference to the pointed value.
|
||||||
|
*/
|
||||||
|
ref T opUnary(string op)()
|
||||||
|
if (op == "*")
|
||||||
|
{
|
||||||
|
return *storage.payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal
|
||||||
|
* storage.
|
||||||
|
*/
|
||||||
|
@property bool isInitialized() const
|
||||||
|
{
|
||||||
|
return storage !is null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: The number of $(D_PSYMBOL RefCounted) instances that share
|
||||||
|
* ownership over the same pointer (including $(D_KEYWORD this)).
|
||||||
|
* If this $(D_PSYMBOL RefCounted) isn't initialized, returns `0`.
|
||||||
|
*/
|
||||||
|
@property size_t count() const
|
||||||
|
{
|
||||||
|
return storage is null ? 0 : storage.counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin DefaultAllocator;
|
||||||
|
alias get this;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator);
|
||||||
|
auto val = rc.get;
|
||||||
|
|
||||||
|
*val = 8;
|
||||||
|
assert(*rc.storage.payload == 8);
|
||||||
|
|
||||||
|
val = null;
|
||||||
|
assert(rc.storage.payload !is null);
|
||||||
|
assert(*rc.storage.payload == 8);
|
||||||
|
|
||||||
|
*rc = 9;
|
||||||
|
assert(*rc.storage.payload == 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
version (unittest)
|
||||||
|
{
|
||||||
|
private class A
|
||||||
|
{
|
||||||
|
uint *destroyed;
|
||||||
|
|
||||||
|
this(ref uint destroyed) @nogc
|
||||||
|
{
|
||||||
|
this.destroyed = &destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
~this() @nogc
|
||||||
|
{
|
||||||
|
++(*destroyed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct B
|
||||||
|
{
|
||||||
|
int prop;
|
||||||
|
@disable this();
|
||||||
|
this(int param1) @nogc
|
||||||
|
{
|
||||||
|
prop = param1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
uint destroyed;
|
||||||
|
auto a = defaultAllocator.make!A(destroyed);
|
||||||
|
|
||||||
|
assert(destroyed == 0);
|
||||||
|
{
|
||||||
|
auto rc = RefCounted!A(a, defaultAllocator);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
|
||||||
|
void func(RefCounted!A rc)
|
||||||
|
{
|
||||||
|
assert(rc.count == 2);
|
||||||
|
}
|
||||||
|
func(rc);
|
||||||
|
|
||||||
|
assert(rc.count == 1);
|
||||||
|
}
|
||||||
|
assert(destroyed == 1);
|
||||||
|
|
||||||
|
RefCounted!int rc;
|
||||||
|
assert(rc.count == 0);
|
||||||
|
rc = defaultAllocator.make!int(8);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
static assert(is(typeof(RefCounted!int.storage.payload) == int*));
|
||||||
|
static assert(is(typeof(RefCounted!A.storage.payload) == A));
|
||||||
|
|
||||||
|
static assert(is(RefCounted!B));
|
||||||
|
static assert(is(RefCounted!A));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new object of type $(D_PARAM T) and wraps it in a
|
||||||
|
* $(D_PSYMBOL RefCounted) using $(D_PARAM args) as the parameter list for
|
||||||
|
* the constructor of $(D_PARAM T).
|
||||||
|
*
|
||||||
|
* This function is more efficient than the using of $(D_PSYMBOL RefCounted)
|
||||||
|
* directly, since it allocates only ones (the internal storage and the
|
||||||
|
* object).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Type of the constructed object.
|
||||||
|
* A = Types of the arguments to the constructor of $(D_PARAM T).
|
||||||
|
* allocator = Allocator.
|
||||||
|
* args = Constructor arguments of $(D_PARAM T).
|
||||||
|
*
|
||||||
|
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
|
||||||
|
*/
|
||||||
|
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
|
||||||
|
if (!is(T == interface) && !isAbstractClass!T
|
||||||
|
&& !isArray!T && !isAssociativeArray!T)
|
||||||
|
{
|
||||||
|
auto rc = typeof(return)(allocator);
|
||||||
|
|
||||||
|
immutable storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
|
||||||
|
immutable size = alignedSize(stateSize!T + storageSize);
|
||||||
|
|
||||||
|
auto mem = (() @trusted => allocator.allocate(size))();
|
||||||
|
if (mem is null)
|
||||||
|
{
|
||||||
|
onOutOfMemoryError();
|
||||||
|
}
|
||||||
|
scope (failure)
|
||||||
|
{
|
||||||
|
() @trusted { allocator.deallocate(mem); }();
|
||||||
|
}
|
||||||
|
rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]);
|
||||||
|
|
||||||
|
static if (is(T == class))
|
||||||
|
{
|
||||||
|
rc.storage.payload = emplace!T(mem[storageSize .. $], args);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
|
||||||
|
rc.storage.payload = emplace!T(ptr, args);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!int(5);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
|
||||||
|
void func(RefCounted!int param)
|
||||||
|
{
|
||||||
|
if (param.count == 2)
|
||||||
|
{
|
||||||
|
func(param);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(param.count == 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func(rc);
|
||||||
|
|
||||||
|
assert(rc.count == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @nogc unittest
|
||||||
|
{
|
||||||
|
struct E
|
||||||
|
{
|
||||||
|
}
|
||||||
|
auto b = defaultAllocator.refCounted!B(15);
|
||||||
|
static assert(is(typeof(b.storage.payload) == B*));
|
||||||
|
static assert(is(typeof(b.prop) == int));
|
||||||
|
static assert(!is(typeof(defaultAllocator.refCounted!B())));
|
||||||
|
|
||||||
|
static assert(is(typeof(defaultAllocator.refCounted!E())));
|
||||||
|
static assert(!is(typeof(defaultAllocator.refCounted!E(5))));
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!B(3);
|
||||||
|
assert(rc.get.prop == 3);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!E();
|
||||||
|
assert(rc.count);
|
||||||
|
}
|
||||||
|
}
|
@ -1,423 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright: Eugene Wissner 2016.
|
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
||||||
* Mozilla Public License, v. 2.0).
|
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
|
||||||
*/
|
|
||||||
module tanya.memory.ullocator;
|
|
||||||
|
|
||||||
import tanya.memory.allocator;
|
|
||||||
|
|
||||||
@nogc:
|
|
||||||
|
|
||||||
version (Posix):
|
|
||||||
|
|
||||||
import core.sys.posix.sys.mman;
|
|
||||||
import core.sys.posix.unistd;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocator for Posix systems with mmap/munmap support.
|
|
||||||
*
|
|
||||||
* This allocator allocates memory in regions (multiple of 4 KB for example).
|
|
||||||
* Each region is then splitted in blocks. So it doesn't request the memory
|
|
||||||
* from the operating system on each call, but only if there are no large
|
|
||||||
* enought free blocks in the available regions.
|
|
||||||
* Deallocation works in the same way. Deallocation doesn't immediately
|
|
||||||
* gives the memory back to the operating system, but marks the appropriate
|
|
||||||
* block as free and only if all blocks in the region are free, the complet
|
|
||||||
* region is deallocated.
|
|
||||||
*
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
* | | | | | || | | |
|
|
||||||
* | |prev <----------- | || | | |
|
|
||||||
* | R | B | | B | || R | B | |
|
|
||||||
* | E | L | | L | next E | L | |
|
|
||||||
* | G | O | DATA | O | FREE ---> G | O | DATA |
|
|
||||||
* | I | C | | C | <--- I | C | |
|
|
||||||
* | O | K | | K | prev O | K | |
|
|
||||||
* | N | -----------> next| || N | | |
|
|
||||||
* | | | | | || | | |
|
|
||||||
* --------------------------------------------------- ------------------------
|
|
||||||
*/
|
|
||||||
class Ullocator : Allocator
|
|
||||||
{
|
|
||||||
@nogc:
|
|
||||||
@disable this();
|
|
||||||
|
|
||||||
shared static this() @safe nothrow
|
|
||||||
{
|
|
||||||
pageSize = sysconf(_SC_PAGE_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocates $(D_PARAM size) bytes of memory.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* size = Amount of memory to allocate.
|
|
||||||
*
|
|
||||||
* Returns: The pointer to the new allocated memory.
|
|
||||||
*/
|
|
||||||
void[] allocate(size_t size) @trusted nothrow
|
|
||||||
{
|
|
||||||
immutable dataSize = addAlignment(size);
|
|
||||||
|
|
||||||
void* data = findBlock(dataSize);
|
|
||||||
if (data is null)
|
|
||||||
{
|
|
||||||
data = initializeRegion(dataSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data is null ? null : data[0..size];
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
auto p = Ullocator.instance.allocate(20);
|
|
||||||
|
|
||||||
assert(p);
|
|
||||||
|
|
||||||
Ullocator.instance.deallocate(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search for a block large enough to keep $(D_PARAM size) and split it
|
|
||||||
* into two blocks if the block is too large.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* size = Minimum size the block should have.
|
|
||||||
*
|
|
||||||
* Returns: Data the block points to or $(D_KEYWORD null).
|
|
||||||
*/
|
|
||||||
private void* findBlock(size_t size) nothrow
|
|
||||||
{
|
|
||||||
Block block1;
|
|
||||||
RegionLoop: for (auto r = head; r !is null; r = r.next)
|
|
||||||
{
|
|
||||||
block1 = cast(Block) (cast(void*) r + regionEntrySize);
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (block1.free && block1.size >= size)
|
|
||||||
{
|
|
||||||
break RegionLoop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while ((block1 = block1.next) !is null);
|
|
||||||
}
|
|
||||||
if (block1 is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else if (block1.size >= size + alignment + blockEntrySize)
|
|
||||||
{ // Split the block if needed
|
|
||||||
Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size);
|
|
||||||
block2.prev = block1;
|
|
||||||
if (block1.next is null)
|
|
||||||
{
|
|
||||||
block2.next = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
block2.next = block1.next.next;
|
|
||||||
}
|
|
||||||
block1.next = block2;
|
|
||||||
|
|
||||||
block1.free = false;
|
|
||||||
block2.free = true;
|
|
||||||
|
|
||||||
block2.size = block1.size - blockEntrySize - size;
|
|
||||||
block1.size = size;
|
|
||||||
|
|
||||||
block2.region = block1.region;
|
|
||||||
++block1.region.blocks;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
block1.free = false;
|
|
||||||
++block1.region.blocks;
|
|
||||||
}
|
|
||||||
return cast(void*) block1 + blockEntrySize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deallocates a memory block.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* p = A pointer to the memory block to be freed.
|
|
||||||
*
|
|
||||||
* Returns: Whether the deallocation was successful.
|
|
||||||
*/
|
|
||||||
bool deallocate(void[] p) @trusted nothrow
|
|
||||||
{
|
|
||||||
if (p is null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Block block = cast(Block) (p.ptr - blockEntrySize);
|
|
||||||
if (block.region.blocks <= 1)
|
|
||||||
{
|
|
||||||
if (block.region.prev !is null)
|
|
||||||
{
|
|
||||||
block.region.prev.next = block.region.next;
|
|
||||||
}
|
|
||||||
else // Replace the list head. It is being deallocated
|
|
||||||
{
|
|
||||||
head = block.region.next;
|
|
||||||
}
|
|
||||||
if (block.region.next !is null)
|
|
||||||
{
|
|
||||||
block.region.next.prev = block.region.prev;
|
|
||||||
}
|
|
||||||
return munmap(block.region, block.region.size) == 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
block.free = true;
|
|
||||||
--block.region.blocks;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
auto p = Ullocator.instance.allocate(20);
|
|
||||||
|
|
||||||
assert(Ullocator.instance.deallocate(p));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases or decreases the size of a memory block.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* p = A pointer to the memory block.
|
|
||||||
* size = Size of the reallocated block.
|
|
||||||
*
|
|
||||||
* Returns: Whether the reallocation was successful.
|
|
||||||
*/
|
|
||||||
bool reallocate(ref void[] p, size_t size) @trusted nothrow
|
|
||||||
{
|
|
||||||
if (size == p.length)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto reallocP = allocate(size);
|
|
||||||
if (reallocP is null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p !is null)
|
|
||||||
{
|
|
||||||
if (size > p.length)
|
|
||||||
{
|
|
||||||
reallocP[0..p.length] = p[0..$];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reallocP[0..size] = p[0..size];
|
|
||||||
}
|
|
||||||
deallocate(p);
|
|
||||||
}
|
|
||||||
p = reallocP;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
void[] p;
|
|
||||||
Ullocator.instance.reallocate(p, 10 * int.sizeof);
|
|
||||||
(cast(int[]) p)[7] = 123;
|
|
||||||
|
|
||||||
assert(p.length == 40);
|
|
||||||
|
|
||||||
Ullocator.instance.reallocate(p, 8 * int.sizeof);
|
|
||||||
|
|
||||||
assert(p.length == 32);
|
|
||||||
assert((cast(int[]) p)[7] == 123);
|
|
||||||
|
|
||||||
Ullocator.instance.reallocate(p, 20 * int.sizeof);
|
|
||||||
(cast(int[]) p)[15] = 8;
|
|
||||||
|
|
||||||
assert(p.length == 80);
|
|
||||||
assert((cast(int[]) p)[15] == 8);
|
|
||||||
assert((cast(int[]) p)[7] == 123);
|
|
||||||
|
|
||||||
Ullocator.instance.reallocate(p, 8 * int.sizeof);
|
|
||||||
|
|
||||||
assert(p.length == 32);
|
|
||||||
assert((cast(int[]) p)[7] == 123);
|
|
||||||
|
|
||||||
Ullocator.instance.deallocate(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static allocator instance and initializer.
|
|
||||||
*
|
|
||||||
* Returns: The global $(D_PSYMBOL Allocator) instance.
|
|
||||||
*/
|
|
||||||
static @property Ullocator instance() @trusted nothrow
|
|
||||||
{
|
|
||||||
if (instance_ is null)
|
|
||||||
{
|
|
||||||
immutable instanceSize = addAlignment(__traits(classInstanceSize, Ullocator));
|
|
||||||
|
|
||||||
Region head; // Will become soon our region list head
|
|
||||||
void* data = initializeRegion(instanceSize, head);
|
|
||||||
|
|
||||||
if (data is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
data[0..instanceSize] = typeid(Ullocator).initializer[];
|
|
||||||
instance_ = cast(Ullocator) data;
|
|
||||||
instance_.head = head;
|
|
||||||
}
|
|
||||||
return instance_;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
assert(instance is instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a region for one element.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* size = Aligned size of the first data block in the region.
|
|
||||||
* head = Region list head.
|
|
||||||
*
|
|
||||||
* Returns: A pointer to the data.
|
|
||||||
*/
|
|
||||||
pragma(inline)
|
|
||||||
private static void* initializeRegion(size_t size,
|
|
||||||
ref Region head) nothrow
|
|
||||||
{
|
|
||||||
immutable regionSize = calculateRegionSize(size);
|
|
||||||
void* p = mmap(null,
|
|
||||||
regionSize,
|
|
||||||
PROT_READ | PROT_WRITE,
|
|
||||||
MAP_PRIVATE | MAP_ANON,
|
|
||||||
-1,
|
|
||||||
0);
|
|
||||||
if (p is MAP_FAILED)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Region region = cast(Region) p;
|
|
||||||
region.blocks = 1;
|
|
||||||
region.size = regionSize;
|
|
||||||
|
|
||||||
// Set the pointer to the head of the region list
|
|
||||||
if (head !is null)
|
|
||||||
{
|
|
||||||
head.prev = region;
|
|
||||||
}
|
|
||||||
region.next = head;
|
|
||||||
region.prev = null;
|
|
||||||
head = region;
|
|
||||||
|
|
||||||
// Initialize the data block
|
|
||||||
void* memoryPointer = p + regionEntrySize;
|
|
||||||
Block block1 = cast(Block) memoryPointer;
|
|
||||||
block1.size = size;
|
|
||||||
block1.free = false;
|
|
||||||
|
|
||||||
// It is what we want to return
|
|
||||||
void* data = memoryPointer + blockEntrySize;
|
|
||||||
|
|
||||||
// Free block after data
|
|
||||||
memoryPointer = data + size;
|
|
||||||
Block block2 = cast(Block) memoryPointer;
|
|
||||||
block1.prev = block2.next = null;
|
|
||||||
block1.next = block2;
|
|
||||||
block2.prev = block1;
|
|
||||||
block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2;
|
|
||||||
block2.free = true;
|
|
||||||
block1.region = block2.region = region;
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ditto.
|
|
||||||
private void* initializeRegion(size_t size) nothrow
|
|
||||||
{
|
|
||||||
return initializeRegion(size, head);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* x = Space to be aligned.
|
|
||||||
*
|
|
||||||
* Returns: Aligned size of $(D_PARAM x).
|
|
||||||
*/
|
|
||||||
pragma(inline)
|
|
||||||
private static immutable(size_t) addAlignment(size_t x) @safe pure nothrow
|
|
||||||
out (result)
|
|
||||||
{
|
|
||||||
assert(result > 0);
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
return (x - 1) / alignment * alignment + alignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* x = Required space.
|
|
||||||
*
|
|
||||||
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
|
|
||||||
*/
|
|
||||||
pragma(inline)
|
|
||||||
private static immutable(size_t) calculateRegionSize(size_t x)
|
|
||||||
@safe pure nothrow
|
|
||||||
out (result)
|
|
||||||
{
|
|
||||||
assert(result > 0);
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
x += regionEntrySize + blockEntrySize * 2;
|
|
||||||
return x / pageSize * pageSize + pageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum alignment = 8;
|
|
||||||
|
|
||||||
private static Ullocator instance_;
|
|
||||||
|
|
||||||
private shared static immutable long pageSize;
|
|
||||||
|
|
||||||
private struct RegionEntry
|
|
||||||
{
|
|
||||||
Region prev;
|
|
||||||
Region next;
|
|
||||||
uint blocks;
|
|
||||||
ulong size;
|
|
||||||
}
|
|
||||||
private alias Region = RegionEntry*;
|
|
||||||
private enum regionEntrySize = 32;
|
|
||||||
|
|
||||||
private Region head;
|
|
||||||
|
|
||||||
private struct BlockEntry
|
|
||||||
{
|
|
||||||
Block prev;
|
|
||||||
Block next;
|
|
||||||
bool free;
|
|
||||||
ulong size;
|
|
||||||
Region region;
|
|
||||||
}
|
|
||||||
private alias Block = BlockEntry*;
|
|
||||||
private enum blockEntrySize = 40;
|
|
||||||
}
|
|
1411
source/tanya/network/socket.d
Normal file
1411
source/tanya/network/socket.d
Normal file
File diff suppressed because it is too large
Load Diff
1189
source/tanya/network/url.d
Normal file
1189
source/tanya/network/url.d
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user