54 Commits

Author SHA1 Message Date
ab930657b6 Queue: Leave only enqueue/dequeue/empty/opApply 2017-01-11 18:24:50 +01:00
291920b479 Vector constructors for initializing from a vector 2017-01-10 06:34:53 +01:00
999c9bdb0f Vector: remove core.stdc.string import 2017-01-09 19:52:39 +01:00
405b6d9f9f Accept only ranges for slicing assignment 2017-01-09 19:32:51 +01:00
87b74b2542 Fix reallocating the vector 2017-01-09 17:03:09 +01:00
d6514cb515 Fix Ddoc 2017-01-07 17:53:57 +01:00
976eb4bfbc Add downloads button 2017-01-07 15:25:05 +01:00
fb843e3473 Fix #3 2017-01-07 09:30:42 +01:00
f3d48234c0 MmapPool: add expand and empty methods. 2017-01-06 23:12:19 +01:00
254b881da6 Fix block size calculation 2017-01-06 11:56:54 +01:00
8e0b742748 MmapPool: Merge blocks on deallocation if possible 2017-01-05 14:25:54 +01:00
a35e04c049 Don't throw in the allocator, return null 2017-01-05 07:42:23 +01:00
4271c8583e Remove static constructor from the MmapPool 2017-01-05 07:35:29 +01:00
e27d0fe58c Fix Vector.remove not destroying from the end copied elements 2017-01-04 20:37:55 +01:00
67952dabdb Implement Vector.remove 2017-01-03 13:21:19 +01:00
b8d5d4c2bd Fix template condition for Vector.insertBack 2017-01-03 10:03:28 +01:00
b6413823cd Add opEquals for all combinations of vector ranges 2017-01-02 17:33:01 +01:00
48e355b87f Vector: allow insert multiple items in insertBack 2017-01-02 12:47:41 +01:00
b3f4ea572e Vector: Use opEquals if defined to compare items 2017-01-02 06:59:05 +01:00
c73e704421 Fix constness of Vector range, optimizing 2017-01-01 02:51:49 +01:00
0561e96f21 Fix build with 2.071.2 2016-12-28 07:57:36 +01:00
86d87430da Fix socket build on Windows 2016-12-25 12:54:04 +01:00
0156c5a883 Don't allocate watcher queue on the heap 2016-12-25 00:54:05 +01:00
c966b42ac3 Fix FreeBSD build 2016-12-24 22:25:34 +01:00
200fff3714 Fix #1 2016-12-22 22:05:48 +01:00
28755b4d01 Rename module traits into enums 2016-12-22 22:05:06 +01:00
8bd6a14988 Fix issue going out of the range with back() 2016-12-22 22:01:45 +01:00
b41dcc9f37 Fix compatibility issue with dmd 2.071 2016-12-22 22:01:11 +01:00
38addb7a5b Add support for pow for big integers 2016-12-22 21:51:16 +01:00
f7fb89fed0 Move random.d into math submodule 2016-12-22 21:50:33 +01:00
e32af2d09e Add scalar type template parameter for buffers 2016-12-19 21:24:28 +01:00
f1bc4dc2e2 Add length and opCmp to the Queue 2016-12-19 16:33:16 +01:00
40857e69b7 Add capacity capabilities to the vector 2016-12-18 18:48:25 +01:00
c1fb89af99 Implement insertion into the vector 2016-12-15 15:00:06 +01:00
061cd6264b Use auto ref for templated overloaded functions 2016-12-13 10:59:05 +01:00
f437dafa6b Fix dispose for structs 2016-12-13 10:58:11 +01:00
54d0597657 Use resizeArray instead of expand/shrinkArray 2016-12-13 10:57:12 +01:00
ab9f96e0c7 Replace class Queue with the struct Queue 2016-12-13 10:56:29 +01:00
711855474c Remove unused buffer interface 2016-12-13 10:54:27 +01:00
b20f367aa8 Array support for refCounted factory function 2016-12-11 11:42:09 +01:00
a2dadda511 Fix subtraction of numbers with different signs 2016-12-08 18:30:22 +01:00
77dca31261 Add license info 2016-12-08 15:07:58 +01:00
b87aed4395 Add travis to README 2016-12-08 15:00:09 +01:00
42bbb3b023 Add travis 2016-12-08 14:58:59 +01:00
4309a30dfe Add opBinary for the other math operations on Integer 2016-12-08 14:51:49 +01:00
9362287938 Fix error with assignin long numbers to Integer 2016-12-08 14:43:50 +01:00
78bd901339 Add short description of the packages to the README 2016-12-07 23:16:49 +01:00
c8e6d44f7b Implement own dispose 2016-12-07 11:01:51 +01:00
f75433e0e6 Implement operations on negative numbers 2016-12-06 23:22:12 +01:00
fa607141e4 Make allocator shared and fix some RefCounted bugs 2016-12-06 21:29:08 +01:00
b3fdd6fd4a Implement unary operation for multiple precision integers 2016-12-05 22:06:06 +01:00
86c08e7af6 Use RefCounted as math.mp.Integer internal storage 2016-12-04 22:51:21 +01:00
1c5796eb96 Add RefCounted 2016-12-04 14:05:53 +01:00
f7f92e7906 Switch to container.queue. Remove PendingQueue 2016-12-02 19:18:37 +01:00
33 changed files with 7409 additions and 5296 deletions

4
.gitignore vendored
View File

@ -4,3 +4,7 @@
# D
.dub
__test__*__
__test__*__.core
/docs/
/docs.json

17
.travis.yml Normal file
View File

@ -0,0 +1,17 @@
sudo: false
os:
- linux
language: d
d:
- dmd-2.072.2
- dmd-2.071.2
env:
matrix:
- ARCH=x86_64
script:
- dub test --arch=$ARCH

View File

@ -1,4 +1,69 @@
# tanya
# Tanya
tanya's mission is to provide a GC-free, general purpose library for D
programming language for developing servers.
[![Build Status](https://travis-ci.org/caraus-ecms/tanya.svg?branch=master)](https://travis-ci.org/caraus-ecms/tanya)
[![Dub version](https://img.shields.io/dub/v/tanya.svg)](https://code.dlang.org/packages/tanya)
[![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya)
[![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](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.
## Overview
Tanya consists of the following packages:
* `async`: Event loop (epoll, kqueue and IOCP).
* `container`: Queue, Vector, Singly linked list, buffers.
* `crypto`: Work in progress TLS implementation.
* `math`: Multiple 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.072.2
* dmd 2.071.2
### Current status
The library is currently under development, but some parts of it can already be
used.
`network` and `async` exist for quite some time and could be better tested than
other components.
Containers were newly reworked and the API won't change significantly, but will
be only extended. The same is true for the `memory` package.
`math` package contains an arbitrary precision integer implementation that has
a stable API (that mostly consists of operator overloads), but still needs
testing and work on its performance.
I'm currently mostly working on `crypto` that is not a complete cryptographic
suite, but contains (will contain) algorithm implementations required by TLS.
### Further characteristics
* Tanya is a native D library.
* Documentation and usage examples can be found in the source code.
Online documentation will be published soon.
* 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.

View File

@ -26,6 +26,13 @@ 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);
}
class EpollLoop : SelectorLoop
{
protected int fd;
@ -34,20 +41,20 @@ class EpollLoop : SelectorLoop
/**
* Initializes the loop.
*/
this()
this() @nogc
{
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
{
throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
}
super();
events = MmapPool.instance.makeArray!epoll_event(maxEvents);
MmapPool.instance.resizeArray(events, maxEvents);
}
/**
* Free loop internals.
*/
~this()
~this() @nogc
{
MmapPool.instance.dispose(events);
close(fd);
@ -63,7 +70,9 @@ class EpollLoop : SelectorLoop
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
protected override bool reify(ConnectionWatcher watcher, EventMask oldEvents, EventMask events)
protected override bool reify(ConnectionWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc
in
{
assert(watcher !is null);
@ -97,7 +106,7 @@ class EpollLoop : SelectorLoop
/**
* Does the actual polling.
*/
protected override void poll()
protected override void poll() @nogc
{
// Don't block
immutable timeout = cast(immutable int) blockTime.total!"msecs";
@ -107,7 +116,7 @@ class EpollLoop : SelectorLoop
{
if (errno != EINTR)
{
throw theAllocator.make!BadLoopException();
throw defaultAllocator.make!BadLoopException();
}
return;
}
@ -150,7 +159,7 @@ class EpollLoop : SelectorLoop
}
else if (io.output.length)
{
swapPendings.insertBack(io);
swapPendings.enqueue(io);
}
}
else if (events[i].events & EPOLLOUT)

View File

@ -30,14 +30,14 @@ class IOCPStreamTransport : StreamTransport
{
private OverlappedConnectedSocket socket_;
private WriteBuffer input;
private WriteBuffer!ubyte input;
/**
* Creates new completion port transport.
* Params:
* socket = Socket.
*/
this(OverlappedConnectedSocket socket)
this(OverlappedConnectedSocket socket) @nogc
in
{
assert(socket !is null);
@ -45,15 +45,11 @@ class IOCPStreamTransport : StreamTransport
body
{
socket_ = socket;
input = MmapPool.instance.make!WriteBuffer();
input = WriteBuffer!ubyte(8192, MmapPool.instance);
}
~this()
{
MmapPool.instance.dispose(input);
}
@property inout(OverlappedConnectedSocket) socket() inout pure nothrow @safe @nogc
@property inout(OverlappedConnectedSocket) socket()
inout pure nothrow @safe @nogc
{
return socket_;
}
@ -64,7 +60,7 @@ class IOCPStreamTransport : StreamTransport
* Params:
* data = Data to send.
*/
void write(ubyte[] data)
void write(ubyte[] data) @nogc
{
immutable empty = input.length == 0;
input ~= data;
@ -94,14 +90,15 @@ class IOCPLoop : Loop
/**
* Initializes the loop.
*/
this()
this() @nogc
{
super();
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (!completionPort)
{
throw theAllocator.make!BadLoopException("Creating completion port failed");
throw make!BadLoopException(defaultAllocator,
"Creating completion port failed");
}
}
@ -117,7 +114,7 @@ class IOCPLoop : Loop
*/
override protected bool reify(ConnectionWatcher watcher,
EventMask oldEvents,
EventMask events)
EventMask events) @nogc
{
SocketState overlapped;
if (!(oldEvents & Event.accept) && (events & Event.accept))
@ -141,7 +138,7 @@ class IOCPLoop : Loop
catch (SocketException e)
{
MmapPool.instance.dispose(overlapped);
theAllocator.dispose(e);
defaultAllocator.dispose(e);
return false;
}
}
@ -173,7 +170,7 @@ class IOCPLoop : Loop
catch (SocketException e)
{
MmapPool.instance.dispose(overlapped);
theAllocator.dispose(e);
defaultAllocator.dispose(e);
return false;
}
}
@ -184,7 +181,7 @@ class IOCPLoop : Loop
/**
* Does the actual polling.
*/
override protected void poll()
override protected void poll() @nogc
{
DWORD lpNumberOfBytes;
ULONG_PTR key;
@ -221,11 +218,11 @@ class IOCPLoop : Loop
auto transport = MmapPool.instance.make!IOCPStreamTransport(socket);
auto io = MmapPool.instance.make!IOWatcher(transport, connection.protocol);
connection.incoming.insertBack(io);
connection.incoming.enqueue(io);
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
swapPendings.insertBack(connection);
swapPendings.enqueue(connection);
listener.beginAccept(overlapped);
break;
case OverlappedSocketEvent.read:
@ -267,7 +264,7 @@ class IOCPLoop : Loop
{
transport.socket.beginReceive(io.output[], overlapped);
}
swapPendings.insertBack(io);
swapPendings.enqueue(io);
}
break;
case OverlappedSocketEvent.write:

View File

@ -6,44 +6,49 @@
* 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)
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.async.event.kqueue;
version (OSX)
{
version = MissingKevent;
version = MacBSD;
}
else version (iOS)
{
version = MissingKevent;
version = MacBSD;
}
else version (TVOS)
{
version = MissingKevent;
version = MacBSD;
}
else version (WatchOS)
{
version = MissingKevent;
version = MacBSD;
}
else version (FreeBSD)
{
version = MacBSD;
}
else version (OpenBSD)
{
version = MissingKevent;
version = MacBSD;
}
else version (DragonFlyBSD)
{
version = MissingKevent;
version = MacBSD;
}
version (MissingKevent)
{
extern (C):
nothrow:
@nogc:
version (MacBSD):
import core.stdc.stdint; // intptr_t, uintptr_t
import core.sys.posix.time; // timespec
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
{
*kevp = kevent_t(args);
}
enum : short
{
EVFILT_READ = -1,
@ -60,11 +65,6 @@ version (MissingKevent)
EVFILT_SYSCOUNT = 11
}
extern(D) void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args)
{
*kevp = kevent_t(args);
}
struct kevent_t
{
uintptr_t ident; /* identifier for this event */
@ -97,35 +97,10 @@ version (MissingKevent)
EV_ERROR = 0x4000, /* error, data contains errno */
}
int kqueue();
int kevent(int kq, const kevent_t *changelist, int nchanges,
kevent_t *eventlist, int nevents,
const timespec *timeout);
}
version (OSX)
{
version = MacBSD;
}
else version (iOS)
{
version = MacBSD;
}
else version (FreeBSD)
{
version = MacBSD;
public import core.sys.freebsd.sys.event;
}
else version (OpenBSD)
{
version = MacBSD;
}
else version (DragonFlyBSD)
{
version = MacBSD;
}
version (MacBSD):
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;
import tanya.async.event.selector;
import tanya.async.loop;
@ -151,12 +126,13 @@ class KqueueLoop : SelectorLoop
* Returns: Maximal event count can be got at a time
* (should be supported by the backend).
*/
override protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc
override protected @property inout(uint) maxEvents()
inout const pure nothrow @safe @nogc
{
return cast(uint) events.length;
}
this()
this() @nogc
{
super();
@ -164,21 +140,21 @@ class KqueueLoop : SelectorLoop
{
throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
}
events = MmapPool.instance.makeArray!kevent_t(64);
changes = MmapPool.instance.makeArray!kevent_t(64);
MmapPool.instance.resizeArray(events, 64);
MmapPool.instance.resizeArray(changes, 64);
}
/**
* Free loop internals.
*/
~this()
~this() @nogc
{
MmapPool.instance.dispose(events);
MmapPool.instance.dispose(changes);
close(fd);
}
private void set(socket_t socket, short filter, ushort flags)
private void set(socket_t socket, short filter, ushort flags) @nogc
{
if (changes.length <= changeCount)
{
@ -206,7 +182,7 @@ class KqueueLoop : SelectorLoop
*/
override protected bool reify(ConnectionWatcher watcher,
EventMask oldEvents,
EventMask events)
EventMask events) @nogc
{
if (events != oldEvents)
{
@ -233,7 +209,7 @@ class KqueueLoop : SelectorLoop
/**
* Does the actual polling.
*/
protected override void poll()
protected override void poll() @nogc
{
timespec ts;
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
@ -250,7 +226,7 @@ class KqueueLoop : SelectorLoop
{
if (errno != EINTR)
{
throw theAllocator.make!BadLoopException();
throw defaultAllocator.make!BadLoopException();
}
return;
}
@ -295,7 +271,7 @@ class KqueueLoop : SelectorLoop
}
else if (io.output.length)
{
swapPendings.insertBack(io);
swapPendings.enqueue(io);
}
}
else if (events[i].filter == EVFILT_WRITE)
@ -316,7 +292,7 @@ class KqueueLoop : SelectorLoop
* Returns: The blocking time.
*/
override protected @property inout(Duration) blockTime()
inout @safe pure nothrow
inout @nogc @safe pure nothrow
{
return min(super.blockTime, 1.dur!"seconds");
}
@ -333,7 +309,8 @@ class KqueueLoop : SelectorLoop
* completed or scheduled, $(D_KEYWORD false) otherwise (the
* transport is be destroyed then).
*/
protected override bool feed(SelectorStreamTransport transport, SocketException exception = null)
protected override bool feed(SelectorStreamTransport transport,
SocketException exception = null) @nogc
{
if (!super.feed(transport, exception))
{

View File

@ -30,7 +30,7 @@ class SelectorStreamTransport : StreamTransport
private ConnectedSocket socket_;
/// Input buffer.
package WriteBuffer input;
package WriteBuffer!ubyte input;
private SelectorLoop loop;
@ -42,19 +42,11 @@ class SelectorStreamTransport : StreamTransport
* loop = Event loop.
* socket = Socket.
*/
this(SelectorLoop loop, ConnectedSocket socket)
this(SelectorLoop loop, ConnectedSocket socket) @nogc
{
socket_ = socket;
this.loop = loop;
input = MmapPool.instance.make!WriteBuffer();
}
/**
* Close the transport and deallocate the data buffers.
*/
~this()
{
MmapPool.instance.dispose(input);
input = WriteBuffer!ubyte(8192, MmapPool.instance);
}
/**
@ -71,7 +63,7 @@ class SelectorStreamTransport : StreamTransport
* Params:
* data = Data to send.
*/
void write(ubyte[] data)
void write(ubyte[] data) @nogc
{
if (!data.length)
{
@ -113,13 +105,13 @@ abstract class SelectorLoop : Loop
/// Pending connections.
protected ConnectionWatcher[] connections;
this()
this() @nogc
{
super();
connections = MmapPool.instance.makeArray!ConnectionWatcher(maxEvents);
MmapPool.instance.resizeArray(connections, maxEvents);
}
~this()
~this() @nogc
{
foreach (ref connection; connections)
{
@ -147,7 +139,8 @@ abstract class SelectorLoop : Loop
* completed or scheduled, $(D_KEYWORD false) otherwise (the
* transport will be destroyed then).
*/
protected bool feed(SelectorStreamTransport transport, SocketException exception = null)
protected bool feed(SelectorStreamTransport transport,
SocketException exception = null) @nogc
{
while (transport.input.length && transport.writeReady)
{
@ -186,7 +179,7 @@ abstract class SelectorLoop : Loop
* Params:
* watcher = Watcher.
*/
override void start(ConnectionWatcher watcher)
override void start(ConnectionWatcher watcher) @nogc
{
if (watcher.active)
{
@ -208,7 +201,7 @@ abstract class SelectorLoop : Loop
* Params:
* connection = Connection watcher ready to accept.
*/
package void acceptConnections(ConnectionWatcher connection)
package void acceptConnections(ConnectionWatcher connection) @nogc
in
{
assert(connection !is null);
@ -224,7 +217,7 @@ abstract class SelectorLoop : Loop
}
catch (SocketException e)
{
theAllocator.dispose(e);
defaultAllocator.dispose(e);
break;
}
if (client is null)
@ -255,12 +248,12 @@ abstract class SelectorLoop : Loop
}
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
connection.incoming.insertBack(io);
connection.incoming.enqueue(io);
}
if (!connection.incoming.empty)
{
swapPendings.insertBack(connection);
swapPendings.enqueue(connection);
}
}
}

View File

@ -10,67 +10,72 @@
*
* ---
* import tanya.async;
* import tanya.memory;
* import tanya.network.socket;
*
* class EchoProtocol : TransmissionControlProtocol
* {
* private DuplexTransport transport;
*
* void received(ubyte[] data)
* void received(ubyte[] data) @nogc
* {
* transport.write(data);
* }
*
* void connected(DuplexTransport transport)
* void connected(DuplexTransport transport) @nogc
* {
* this.transport = transport;
* }
*
* void disconnected(SocketException e = null)
* void disconnected(SocketException e = null) @nogc
* {
* }
* }
*
* void main()
* {
* auto address = new InternetAddress("127.0.0.1", cast(ushort) 8192);
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192);
*
* version (Windows)
* {
* auto sock = new OverlappedStreamSocket(AddressFamily.INET);
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET);
* }
* else
* {
* auto sock = new StreamSocket(AddressFamily.INET);
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET);
* sock.blocking = false;
* }
*
* sock.bind(address);
* sock.listen(5);
*
* auto io = new ConnectionWatcher(sock);
* 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 tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.buffer;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
import core.time;
import std.algorithm.iteration;
import std.algorithm.mutation;
import std.typecons;
import tanya.async.protocol;
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)
{
@ -126,15 +131,17 @@ alias EventMask = BitFlags!Event;
abstract class Loop
{
/// Pending watchers.
protected PendingQueue!Watcher pendings;
protected Queue!Watcher* pendings;
protected PendingQueue!Watcher swapPendings;
/// Ditto.
protected Queue!Watcher* swapPendings;
/**
* Returns: Maximal event count can be got at a time
* (should be supported by the backend).
*/
protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc
protected @property inout(uint) maxEvents()
inout const pure nothrow @safe @nogc
{
return 128U;
}
@ -142,25 +149,34 @@ abstract class Loop
/**
* Initializes the loop.
*/
this()
this() @nogc
{
pendings = MmapPool.instance.make!(PendingQueue!Watcher);
swapPendings = MmapPool.instance.make!(PendingQueue!Watcher);
pendings = MmapPool.instance.make!(Queue!Watcher)(MmapPool.instance);
swapPendings = MmapPool.instance.make!(Queue!Watcher)(MmapPool.instance);
}
/**
* Frees loop internals.
*/
~this()
~this() @nogc
{
foreach (w; *pendings)
{
MmapPool.instance.dispose(w);
}
MmapPool.instance.dispose(pendings);
foreach (w; *swapPendings)
{
MmapPool.instance.dispose(w);
}
MmapPool.instance.dispose(swapPendings);
}
/**
* Starts the loop.
*/
void run()
void run() @nogc
{
done_ = false;
do
@ -168,8 +184,10 @@ abstract class Loop
poll();
// Invoke pendings
swapPendings.each!((ref p) => p.invoke());
foreach (ref w; *swapPendings)
{
w.invoke();
}
swap(pendings, swapPendings);
}
while (!done_);
@ -178,7 +196,7 @@ abstract class Loop
/**
* Break out of the loop.
*/
void unloop() @safe pure nothrow
void unloop() @safe pure nothrow @nogc
{
done_ = true;
}
@ -189,7 +207,7 @@ abstract class Loop
* Params:
* watcher = Watcher.
*/
void start(ConnectionWatcher watcher)
void start(ConnectionWatcher watcher) @nogc
{
if (watcher.active)
{
@ -205,7 +223,7 @@ abstract class Loop
* Params:
* watcher = Watcher.
*/
void stop(ConnectionWatcher watcher)
void stop(ConnectionWatcher watcher) @nogc
{
if (!watcher.active)
{
@ -228,13 +246,13 @@ abstract class Loop
*/
abstract protected bool reify(ConnectionWatcher watcher,
EventMask oldEvents,
EventMask events);
EventMask events) @nogc;
/**
* Returns: The blocking time.
*/
protected @property inout(Duration) blockTime()
inout @safe pure nothrow
inout @safe pure nothrow @nogc
{
// Don't block if we have to do.
return swapPendings.empty ? blockTime_ : Duration.zero;
@ -247,7 +265,7 @@ abstract class Loop
* blockTime = The blocking time. Cannot be larger than
* $(D_PSYMBOL maxBlockTime).
*/
protected @property void blockTime(in Duration blockTime) @safe pure nothrow
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
in
{
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
@ -261,19 +279,19 @@ abstract class Loop
/**
* Kills the watcher and closes the connection.
*/
protected void kill(IOWatcher watcher, SocketException exception)
protected void kill(IOWatcher watcher, SocketException exception) @nogc
{
watcher.socket.shutdown();
theAllocator.dispose(watcher.socket);
defaultAllocator.dispose(watcher.socket);
MmapPool.instance.dispose(watcher.transport);
watcher.exception = exception;
swapPendings.insertBack(watcher);
swapPendings.enqueue(watcher);
}
/**
* Does the actual polling.
*/
abstract protected void poll();
abstract protected void poll() @nogc;
/// Whether the event loop should be stopped.
private bool done_;
@ -287,7 +305,6 @@ abstract class Loop
*/
class BadLoopException : Exception
{
@nogc:
/**
* Params:
* file = The file where the exception occurred.
@ -295,7 +312,7 @@ class BadLoopException : Exception
* 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
pure nothrow const @safe @nogc
{
super("Event loop cannot be initialized.", file, line, next);
}
@ -308,7 +325,7 @@ class BadLoopException : Exception
*
* Returns: The default event loop.
*/
@property Loop defaultLoop()
@property Loop defaultLoop() @nogc
{
if (defaultLoop_ !is null)
{
@ -341,7 +358,7 @@ class BadLoopException : Exception
* Params:
* loop = The event loop.
*/
@property void defaultLoop(Loop loop)
@property void defaultLoop(Loop loop) @nogc
in
{
assert(loop !is null);
@ -352,132 +369,3 @@ body
}
private Loop defaultLoop_;
/**
* Queue.
*
* Params:
* T = Content type.
*/
class PendingQueue(T)
{
/**
* Creates a new $(D_PSYMBOL Queue).
*/
this()
{
}
/**
* Removes all elements from the queue.
*/
~this()
{
foreach (e; this)
{
MmapPool.instance.dispose(e);
}
}
/**
* Returns: First element.
*/
@property ref T front()
in
{
assert(!empty);
}
body
{
return first.next.content;
}
/**
* Inserts a new element.
*
* Params:
* x = New element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) insertBack(T x)
{
Entry* temp = MmapPool.instance.make!Entry;
temp.content = x;
if (empty)
{
first.next = rear = temp;
}
else
{
rear.next = temp;
rear = rear.next;
}
return this;
}
alias insert = insertBack;
/**
* Inserts a new element.
*
* Params:
* x = New element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) opOpAssign(string Op)(ref T x)
if (Op == "~")
{
return insertBack(x);
}
/**
* Returns: $(D_KEYWORD true) if the queue is empty.
*/
@property bool empty() const @safe pure nothrow
{
return first.next is null;
}
/**
* Move position to the next element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) popFront()
in
{
assert(!empty);
}
body
{
auto n = first.next.next;
MmapPool.instance.dispose(first.next);
first.next = n;
return this;
}
/**
* Queue entry.
*/
protected struct Entry
{
/// Queue item content.
T content;
/// Next list item.
Entry* next;
}
/// The first element of the list.
protected Entry first;
/// The last element of the list.
protected Entry* rear;
}

View File

@ -22,7 +22,7 @@ interface Protocol
* Params:
* data = Read data.
*/
void received(ubyte[] data);
void received(ubyte[] data) @nogc;
/**
* Called when a connection is made.
@ -30,7 +30,7 @@ interface Protocol
* Params:
* transport = Protocol transport.
*/
void connected(DuplexTransport transport);
void connected(DuplexTransport transport) @nogc;
/**
* Called when a connection is lost.
@ -39,7 +39,7 @@ interface Protocol
* exception = $(D_PSYMBOL Exception) if an error caused
* the disconnect, $(D_KEYWORD null) otherwise.
*/
void disconnected(SocketException exception = null);
void disconnected(SocketException exception = null) @nogc;
}
/**

View File

@ -37,7 +37,7 @@ interface WriteTransport : Transport
* Params:
* data = Data to send.
*/
void write(ubyte[] data);
void write(ubyte[] data) @nogc;
}
/**

View File

@ -10,15 +10,16 @@
*/
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;
import std.functional;
import std.exception;
version (Windows)
{
@ -41,7 +42,7 @@ abstract class Watcher
/**
* Invoke some action on event.
*/
void invoke();
void invoke() @nogc;
}
class ConnectionWatcher : Watcher
@ -50,37 +51,39 @@ class ConnectionWatcher : Watcher
private Socket socket_;
/// Protocol factory.
protected Protocol delegate() protocolFactory;
protected Protocol delegate() @nogc protocolFactory;
package PendingQueue!IOWatcher incoming;
package Queue!IOWatcher incoming;
/**
* Params:
* socket = Socket.
*/
this(Socket socket)
this(Socket socket) @nogc
{
socket_ = socket;
incoming = MmapPool.instance.make!(PendingQueue!IOWatcher);
}
/// Ditto.
protected this()
protected this() pure nothrow @safe @nogc
{
}
~this()
~this() @nogc
{
MmapPool.instance.dispose(incoming);
foreach (w; incoming)
{
MmapPool.instance.dispose(w);
}
}
/*
/**
* Params:
* P = Protocol should be used.
*/
void setProtocol(P : Protocol)()
void setProtocol(P : Protocol)() @nogc
{
this.protocolFactory = () => cast(Protocol) MmapPool.instance.make!P;
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
}
/**
@ -94,7 +97,7 @@ class ConnectionWatcher : Watcher
/**
* Returns: New protocol instance.
*/
@property Protocol protocol()
@property Protocol protocol() @nogc
in
{
assert(protocolFactory !is null, "Protocol isn't set.");
@ -107,7 +110,7 @@ class ConnectionWatcher : Watcher
/**
* Invokes new connection callback.
*/
override void invoke()
override void invoke() @nogc
{
foreach (io; incoming)
{
@ -134,14 +137,14 @@ class IOWatcher : ConnectionWatcher
/**
* Returns: Underlying output buffer.
*/
package ReadBuffer output;
package ReadBuffer!ubyte output;
/**
* Params:
* transport = Transport.
* protocol = New instance of the application protocol.
*/
this(StreamTransport transport, Protocol protocol)
this(StreamTransport transport, Protocol protocol) @nogc
in
{
assert(transport !is null);
@ -152,16 +155,15 @@ class IOWatcher : ConnectionWatcher
super();
transport_ = transport;
protocol_ = protocol;
output = MmapPool.instance.make!ReadBuffer();
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
active = true;
}
/**
* Destroys the watcher.
*/
protected ~this()
~this() @nogc
{
MmapPool.instance.dispose(output);
MmapPool.instance.dispose(protocol_);
}
@ -174,7 +176,8 @@ class IOWatcher : ConnectionWatcher
*
* Returns: $(D_KEYWORD this).
*/
IOWatcher opCall(StreamTransport transport, Protocol protocol) pure nothrow @nogc
IOWatcher opCall(StreamTransport transport, Protocol protocol)
pure nothrow @nogc
in
{
assert(transport !is null);
@ -226,7 +229,7 @@ class IOWatcher : ConnectionWatcher
/**
* Invokes the watcher callback.
*/
override void invoke()
override void invoke() @nogc
{
if (output.length)
{

View File

@ -10,6 +10,7 @@
*/
module tanya.container.buffer;
import std.traits;
import tanya.memory;
version (unittest)
@ -33,51 +34,6 @@ version (unittest)
}
}
/**
* Interface for implemeting input/output buffers.
*/
interface Buffer
{
/**
* Returns: The size of the internal buffer.
*/
@property size_t capacity() const @nogc @safe pure nothrow;
/**
* Returns: Data size.
*/
@property size_t length() const @nogc @safe pure nothrow;
/**
* Returns: Available space.
*/
@property size_t free() const @nogc @safe pure nothrow;
/**
* Params:
* start = Start position.
* end = End position.
*
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
*/
@property ubyte[] opSlice(size_t start, size_t end)
in
{
assert(start <= end);
assert(end <= length);
}
/**
* Returns: Length of available data.
*/
@property size_t opDollar() const pure nothrow @safe @nogc;
/**
* Returns: Data chunk.
*/
@property ubyte[] opIndex();
}
/**
* Self-expanding buffer, that can be used with functions returning the number
* of the read bytes.
@ -87,26 +43,30 @@ interface Buffer
* available data. But only one asynchronous call at a time is supported. Be
* sure to call $(D_PSYMBOL ReadBuffer.clear()) before you append the result
* of the pended asynchronous call.
*
* Params:
* T = Buffer type.
*/
class ReadBuffer : Buffer
struct ReadBuffer(T = ubyte)
if (isScalarType!T)
{
/// Internal buffer.
protected ubyte[] buffer_;
private T[] buffer_;
/// Filled buffer length.
protected size_t length_;
private size_t length_;
/// Start of available data.
protected size_t start;
private size_t start;
/// Last position returned with $(D_KEYWORD []).
protected size_t ring;
private size_t ring;
/// Available space.
protected immutable size_t minAvailable;
private immutable size_t minAvailable = 1024;
/// Size by which the buffer will grow.
protected immutable size_t blockSize;
private immutable size_t blockSize = 8192;
invariant
{
@ -123,39 +83,50 @@ class ReadBuffer : Buffer
* will grow.
* minAvailable = minimal size should be always available to fill.
* So it will reallocate if $(D_INLINECODE
* $(D_PSYMBOL free) < $(D_PARAM minAvailable)
* ).
* $(D_PSYMBOL free) < $(D_PARAM minAvailable)).
* allocator = Allocator.
*/
this(size_t size = 8192,
size_t minAvailable = 1024)
this(in size_t size,
in size_t minAvailable = 1024,
shared Allocator allocator = defaultAllocator) @trusted
{
this(allocator);
this.minAvailable = minAvailable;
this.blockSize = size;
theAllocator.resizeArray!ubyte(buffer_, size);
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
}
/// Ditto.
this(shared Allocator allocator)
in
{
assert(allocator_ is null);
}
body
{
allocator_ = allocator;
}
/**
* Deallocates the internal buffer.
*/
~this()
~this() @trusted
{
theAllocator.dispose(buffer_);
allocator.deallocate(buffer_);
}
///
unittest
{
auto b = theAllocator.make!ReadBuffer;
assert(b.capacity == 8192);
ReadBuffer!ubyte b;
assert(b.capacity == 0);
assert(b.length == 0);
theAllocator.dispose(b);
}
/**
* Returns: The size of the internal buffer.
*/
@property size_t capacity() const @nogc @safe pure nothrow
@property size_t capacity() const
{
return buffer_.length;
}
@ -163,26 +134,28 @@ class ReadBuffer : Buffer
/**
* Returns: Data size.
*/
@property size_t length() const @nogc @safe pure nothrow
@property size_t length() const
{
return length_ - start;
}
/// Ditto.
alias opDollar = length;
/**
* Clears the buffer.
*
* Returns: $(D_KEYWORD this).
*/
ReadBuffer clear() pure nothrow @safe @nogc
void clear()
{
start = length_ = ring;
return this;
}
/**
* Returns: Available space.
*/
@property size_t free() const pure nothrow @safe @nogc
@property size_t free() const
{
return length > ring ? capacity - length : capacity - ring;
}
@ -190,19 +163,17 @@ class ReadBuffer : Buffer
///
unittest
{
auto b = theAllocator.make!ReadBuffer;
ReadBuffer!ubyte b;
size_t numberRead;
// Fills the buffer with values 0..10
assert(b.free == b.blockSize);
assert(b.free == 0);
// Fills the buffer with values 0..10
numberRead = fillBuffer(b[], b.free, 0, 10);
b += numberRead;
assert(b.free == b.blockSize - numberRead);
b.clear();
assert(b.free == b.blockSize);
theAllocator.dispose(b);
}
/**
@ -213,7 +184,7 @@ class ReadBuffer : Buffer
*
* Returns: $(D_KEYWORD this).
*/
ReadBuffer opOpAssign(string op)(size_t length)
ref ReadBuffer opOpAssign(string op)(in size_t length)
if (op == "+")
{
length_ += length;
@ -224,7 +195,7 @@ class ReadBuffer : Buffer
///
unittest
{
auto b = theAllocator.make!ReadBuffer;
ReadBuffer!ubyte b;
size_t numberRead;
ubyte[] result;
@ -251,16 +222,6 @@ class ReadBuffer : Buffer
assert(result[9] == 9);
assert(result[10] == 20);
assert(result[14] == 24);
theAllocator.dispose(b);
}
/**
* Returns: Length of available data.
*/
@property size_t opDollar() const pure nothrow @safe @nogc
{
return length;
}
/**
@ -270,7 +231,7 @@ class ReadBuffer : Buffer
*
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
*/
@property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc
T[] opSlice(in size_t start, in size_t end)
{
return buffer_[this.start + start .. this.start + end];
}
@ -282,7 +243,7 @@ class ReadBuffer : Buffer
*
* Returns: A free chunk of the buffer.
*/
ubyte[] opIndex()
T[] opIndex()
{
if (start > 0)
{
@ -294,7 +255,13 @@ class ReadBuffer : Buffer
{
if (capacity - length < minAvailable)
{
theAllocator.resizeArray!ubyte(buffer_, capacity + blockSize);
void[] buf = buffer_;
immutable cap = capacity;
() @trusted {
allocator.reallocate(buf, (cap + blockSize) * T.sizeof);
buffer_ = cast(T[]) buf;
}();
buffer_[cap .. $] = T.init;
}
ring = length_;
return buffer_[length_ .. $];
@ -304,7 +271,7 @@ class ReadBuffer : Buffer
///
unittest
{
auto b = theAllocator.make!ReadBuffer;
ReadBuffer!ubyte b;
size_t numberRead;
ubyte[] result;
@ -318,54 +285,83 @@ class ReadBuffer : Buffer
assert(result[9] == 9);
b.clear();
assert(b.length == 0);
theAllocator.dispose(b);
}
mixin DefaultAllocator;
}
private unittest
{
static assert(is(ReadBuffer!int));
}
/**
* Circular, self-expanding buffer with overflow support. Can be used with
* functions returning returning the number of the transferred bytes.
* functions returning the number of the transferred bytes.
*
* The buffer is optimized for situations where you read all the data from it
* at once (without writing to it occasionally). It can become ineffective if
* you permanently keep some data in the buffer and alternate writing and
* reading, because it may allocate and move elements.
*
* Params:
* T = Buffer type.
*/
class WriteBuffer : Buffer
struct WriteBuffer(T = ubyte)
if (isScalarType!T)
{
/// Internal buffer.
protected ubyte[] buffer_;
private T[] buffer_;
/// Buffer start position.
protected size_t start;
private size_t start;
/// Buffer ring area size. After this position begins buffer overflow area.
protected size_t ring;
private size_t ring;
/// Size by which the buffer will grow.
protected immutable size_t blockSize;
private immutable size_t blockSize;
/// The position of the free area in the buffer.
protected size_t position;
private size_t position;
invariant
{
assert(blockSize > 0);
// position can refer to an element outside the buffer if the buffer is full.
// Position can refer to an element outside the buffer if the buffer is full.
assert(position <= buffer_.length);
}
/**
* Params:
* size = Initial buffer size and the size by which the buffer
* will grow.
* size = Initial buffer size and the size by which the buffer will
* grow.
* allocator = Allocator.
*/
this(size_t size = 8192)
this(in size_t size, shared Allocator allocator = defaultAllocator) @trusted
in
{
assert(size > 0);
}
body
{
blockSize = size;
ring = size - 1;
theAllocator.resizeArray!ubyte(buffer_, size);
this(allocator);
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
}
@disable this();
/// Ditto.
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
{
allocator_ = allocator;
}
/**
@ -373,26 +369,26 @@ class WriteBuffer : Buffer
*/
~this()
{
theAllocator.dispose(buffer_);
allocator.deallocate(buffer_);
}
/**
* Returns: The size of the internal buffer.
*/
@property size_t capacity() const @nogc @safe pure nothrow
@property size_t capacity() const
{
return buffer_.length;
}
/**
* Note that $(D_PSYMBOL length) doesn't return the real length of the data,
* but only the array length that will be returned with $(D_PSYMBOL buffer)
* next time. Be sure to call $(D_PSYMBOL buffer) and set $(D_KEYWORD +=)
* but only the array length that will be returned with $(D_PSYMBOL opIndex)
* next time. Be sure to call $(D_PSYMBOL opIndex) and set $(D_KEYWORD +=)
* until $(D_PSYMBOL length) returns 0.
*
* Returns: Data size.
*/
@property size_t length() const @nogc @safe pure nothrow
@property size_t length() const
{
if (position > ring || position < start) // Buffer overflowed
{
@ -404,18 +400,13 @@ class WriteBuffer : Buffer
}
}
/**
* Returns: Length of available data.
*/
@property size_t opDollar() const pure nothrow @safe @nogc
{
return length;
}
/// Ditto.
alias opDollar = length;
///
unittest
{
auto b = theAllocator.make!WriteBuffer(4);
auto b = WriteBuffer!ubyte(4);
ubyte[3] buf = [48, 23, 255];
b ~= buf;
@ -432,14 +423,12 @@ class WriteBuffer : Buffer
assert(b.length == 5);
b += b.length;
assert(b.length == 0);
theAllocator.dispose(b);
}
/**
* Returns: Available space.
*/
@property size_t free() const @nogc @safe pure nothrow
@property size_t free() const
{
return capacity - length;
}
@ -448,9 +437,9 @@ class WriteBuffer : Buffer
* Appends data to the buffer.
*
* Params:
* buffer = Buffer chunk got with $(D_PSYMBOL buffer).
* buffer = Buffer chunk got with $(D_PSYMBOL opIndex).
*/
WriteBuffer opOpAssign(string op)(ubyte[] buffer)
ref WriteBuffer opOpAssign(string op)(in T[] buffer)
if (op == "~")
{
size_t end, start;
@ -496,9 +485,12 @@ class WriteBuffer : Buffer
end = position + buffer.length - start;
if (end > capacity)
{
auto newSize = end / blockSize * blockSize + blockSize;
theAllocator.resizeArray!ubyte(buffer_, newSize);
auto newSize = (end / blockSize * blockSize + blockSize) * T.sizeof;
() @trusted {
void[] buf = buffer_;
allocator.reallocate(buf, newSize);
buffer_ = cast(T[]) buf;
}();
}
buffer_[position .. end] = buffer[start .. $];
position = end;
@ -514,7 +506,7 @@ class WriteBuffer : Buffer
///
unittest
{
auto b = theAllocator.make!WriteBuffer(4);
auto b = WriteBuffer!ubyte(4);
ubyte[3] buf = [48, 23, 255];
b ~= buf;
@ -532,31 +524,31 @@ class WriteBuffer : Buffer
assert(b.capacity == 8);
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
&& b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);
}
theAllocator.dispose(b);
b = make!WriteBuffer(theAllocator, 2);
///
unittest
{
auto b = WriteBuffer!ubyte(2);
ubyte[3] buf = [48, 23, 255];
b ~= buf;
assert(b.start == 0);
assert(b.capacity == 4);
assert(b.ring == 3);
assert(b.position == 3);
theAllocator.dispose(b);
}
/**
* Sets how many bytes were written. It will shrink the buffer
* appropriately. Always set this property after calling
* $(D_PSYMBOL buffer).
* appropriately. Always call it after $(D_PSYMBOL opIndex).
*
* Params:
* length = Length of the written data.
*
* Returns: $(D_KEYWORD this).
*/
@property WriteBuffer opOpAssign(string op)(size_t length) pure nothrow @safe @nogc
ref WriteBuffer opOpAssign(string op)(in size_t length)
if (op == "+")
in
{
@ -583,9 +575,11 @@ class WriteBuffer : Buffer
{
auto overflow = position - afterRing;
if (overflow > length) {
buffer_[start.. start + length] = buffer_[afterRing.. afterRing + length];
buffer_[afterRing.. afterRing + length] = buffer_[afterRing + length ..position];
if (overflow > length)
{
immutable afterLength = afterRing + length;
buffer_[start .. start + length] = buffer_[afterRing .. afterLength];
buffer_[afterRing .. afterLength] = buffer_[afterLength .. position];
position -= length;
}
else if (overflow == length)
@ -620,7 +614,7 @@ class WriteBuffer : Buffer
///
unittest
{
auto b = theAllocator.make!WriteBuffer;
auto b = WriteBuffer!ubyte(6);
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
b ~= buf;
@ -629,8 +623,6 @@ class WriteBuffer : Buffer
assert(b.length == 4);
b += 4;
assert(b.length == 0);
theAllocator.dispose(b);
}
/**
@ -639,14 +631,14 @@ class WriteBuffer : Buffer
* After calling it, set $(D_KEYWORD +=) to the length could be
* written.
*
* $(D_PSYMBOL buffer) may return only part of the data. You may need
* to call it (and set $(D_KEYWORD +=) several times until
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
* to call it and set $(D_KEYWORD +=) several times until
* $(D_PSYMBOL length) is 0. If all the data can be written,
* maximally 3 calls are required.
*
* Returns: A chunk of data buffer.
*/
@property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc
T[] opSlice(in size_t start, in size_t end)
{
immutable internStart = this.start + start;
@ -663,7 +655,7 @@ class WriteBuffer : Buffer
///
unittest
{
auto b = theAllocator.make!WriteBuffer(6);
auto b = WriteBuffer!ubyte(6);
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
b ~= buf;
@ -678,23 +670,28 @@ class WriteBuffer : Buffer
assert(b[0 .. $] == buf[0 .. 6]);
b += b.length;
theAllocator.dispose(b);
}
/**
* After calling it, set $(D_KEYWORD +=) to the length could be
* written.
*
* $(D_PSYMBOL buffer) may return only part of the data. You may need
* to call it (and set $(D_KEYWORD +=) several times until
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
* to call it and set $(D_KEYWORD +=) several times until
* $(D_PSYMBOL length) is 0. If all the data can be written,
* maximally 3 calls are required.
*
* Returns: A chunk of data buffer.
*/
@property ubyte[] opIndex() pure nothrow @safe @nogc
T[] opIndex()
{
return opSlice(0, length);
}
mixin DefaultAllocator;
}
private unittest
{
static assert(is(typeof(WriteBuffer!int(5))));
}

View File

@ -0,0 +1,45 @@
/* 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/. */
/*
* Internal package used by containers that rely on entries/nodes.
*
* 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.container.entry;
version (unittest)
{
package struct ConstEqualsStruct
{
int opEquals(typeof(this) that) const @nogc
{
return true;
}
}
package struct MutableEqualsStruct
{
int opEquals(typeof(this) that) @nogc
{
return true;
}
}
package struct NoEqualsStruct
{
}
}
package struct Entry(T)
{
/// Item content.
T content;
/// Next item.
Entry* next;
}

View File

@ -10,28 +10,17 @@
*/
module tanya.container.list;
import tanya.container.entry;
import tanya.memory;
/**
* Singly linked list.
* Singly-linked list.
*
* Params:
* T = Content type.
*/
class SList(T)
struct SList(T)
{
/**
* Creates a new $(D_PSYMBOL SList).
*
* Params:
* allocator = The allocator should be used for the element
* allocations.
*/
this(IAllocator allocator = theAllocator)
{
this.allocator = allocator;
}
/**
* Removes all elements from the list.
*/
@ -41,7 +30,7 @@ class SList(T)
}
/**
* Remove all contents from the $(D_PSYMBOL SList).
* Removes all contents from the list.
*/
void clear()
{
@ -54,14 +43,12 @@ class SList(T)
///
unittest
{
auto l = make!(SList!int)(theAllocator);
SList!int l;
l.insertFront(8);
l.insertFront(5);
l.clear();
assert(l.empty);
dispose(theAllocator, l);
}
/**
@ -83,35 +70,39 @@ class SList(T)
* Params:
* x = New element.
*/
void insertFront(T x)
void insertFront(ref T x)
{
Entry* temp = make!Entry(allocator);
auto temp = allocator.make!(Entry!T);
temp.content = x;
temp.next = first.next;
first.next = temp;
}
/// Ditto.
void insertFront(T x)
{
insertFront(x);
}
/// Ditto.
alias insert = insertFront;
///
unittest
{
auto l = make!(SList!int)(theAllocator);
SList!int l;
l.insertFront(8);
assert(l.front == 8);
l.insertFront(9);
assert(l.front == 9);
dispose(theAllocator, l);
}
/**
* Returns: $(D_KEYWORD true) if the list is empty.
*/
@property bool empty() inout const
@property bool empty() const
{
return first.next is null;
}
@ -121,7 +112,7 @@ class SList(T)
*
* Returns: The first element.
*/
T popFront()
void popFront()
in
{
assert(!empty);
@ -129,26 +120,21 @@ class SList(T)
body
{
auto n = first.next.next;
auto content = first.next.content;
dispose(allocator, first.next);
allocator.dispose(first.next);
first.next = n;
return content;
}
///
unittest
{
auto l = make!(SList!int)(theAllocator);
SList!int l;
l.insertFront(8);
l.insertFront(9);
assert(l.front == 9);
l.popFront();
assert(l.front == 8);
dispose(theAllocator, l);
}
/**
@ -179,7 +165,7 @@ class SList(T)
///
unittest
{
auto l = make!(SList!int)(theAllocator);
SList!int l;
l.insertFront(8);
l.insertFront(5);
@ -188,8 +174,6 @@ class SList(T)
assert(l.removeFront(2) == 2);
assert(l.removeFront(3) == 1);
assert(l.removeFront(3) == 0);
dispose(theAllocator, l);
}
/**
@ -235,7 +219,7 @@ class SList(T)
///
unittest
{
auto l = make!(SList!int)(theAllocator);
SList!int l;
l.insertFront(5);
l.insertFront(4);
@ -246,32 +230,18 @@ class SList(T)
assert(i != 1 || e == 4);
assert(i != 2 || e == 5);
}
dispose(theAllocator, l);
}
/**
* List entry.
*/
protected struct Entry
{
/// List item content.
T content;
/// Next list item.
Entry* next;
}
/// 0th element of the list.
protected Entry first;
private Entry!T first;
/// Allocator.
protected IAllocator allocator;
mixin DefaultAllocator;
}
///
unittest
{
auto l = make!(SList!int)(theAllocator);
SList!int l;
size_t i;
l.insertFront(5);
@ -285,8 +255,6 @@ unittest
++i;
}
assert(i == 3);
dispose(theAllocator, l);
}
private unittest
@ -294,8 +262,5 @@ private unittest
interface Stuff
{
}
auto l = make!(SList!Stuff)(theAllocator);
dispose(theAllocator, l);
static assert(is(SList!Stuff));
}

View File

@ -10,7 +10,6 @@
*/
module tanya.container;
public import tanya.container.bit;
public import tanya.container.buffer;
public import tanya.container.list;
public import tanya.container.vector;

View File

@ -6,68 +6,153 @@
* 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)
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.container.queue;
import std.traits;
import std.algorithm.mutation;
import tanya.container.entry;
import tanya.memory;
/**
* Queue.
* FIFO queue.
*
* Params:
* T = Content type.
*/
class Queue(T)
struct Queue(T)
{
/**
* Creates a new $(D_PSYMBOL Queue).
*
* Params:
* allocator = The allocator should be used for the element
* allocations.
*/
this(IAllocator allocator = theAllocator)
{
this.allocator = allocator;
}
/**
* Removes all elements from the queue.
*/
~this()
{
clear();
while (!empty)
{
dequeue();
}
}
/**
* Removes all elements from the queue.
*/
deprecated
void clear()
{
while (!empty)
{
popFront();
dequeue();
}
}
/**
* 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.
*/
size_t length() const
{
size_t len;
for (const(Entry!T)* i = first.next; i !is null; i = i.next)
{
++len;
}
return len;
}
///
unittest
{
auto q = theAllocator.make!(Queue!int);
Queue!int q;
assert(q.empty);
q.insertBack(8);
q.insertBack(9);
q.clear();
assert(q.empty);
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);
theAllocator.dispose(q);
q.dequeue();
assert(q.length == 2);
q.dequeue();
assert(q.length == 1);
q.dequeue();
assert(q.length == 0);
}
version (D_Ddoc)
{
/**
* Compares two queues. Checks if all elements of the both queues are equal.
*
* Returns: Whether $(D_KEYWORD this) and $(D_PARAM that) are equal.
*/
deprecated
int opEquals(ref typeof(this) that);
/// Ditto.
deprecated
int opEquals(typeof(this) that);
}
else static if (!hasMember!(T, "opEquals")
|| (functionAttributes!(T.opEquals) & FunctionAttribute.const_))
{
deprecated
bool opEquals(in ref typeof(this) that) const
{
const(Entry!T)* i = first.next;
const(Entry!T)* j = that.first.next;
while (i !is null && j !is null)
{
if (i.content != j.content)
{
return false;
}
i = i.next;
j = j.next;
}
return i is null && j is null;
}
deprecated
bool opEquals(in typeof(this) that) const
{
return opEquals(that);
}
}
else
{
deprecated
bool opEquals(ref typeof(this) that)
{
Entry!T* i = first.next;
Entry!T* j = that.first.next;
while (i !is null && j !is null)
{
if (i.content != j.content)
{
return false;
}
i = i.next;
j = j.next;
}
return i is null && j is null;
}
deprecated
bool opEquals(typeof(this) that)
{
return opEquals(that);
}
}
/**
* Returns: First element.
*/
deprecated("Use dequeue instead.")
@property ref inout(T) front() inout
in
{
@ -83,13 +168,12 @@ class Queue(T)
*
* Params:
* x = New element.
*
* Returns: $(D_KEYWORD this).
*/
void insertBack(T x)
ref typeof(this) enqueue(ref T x)
{
Entry* temp = make!Entry(allocator);
temp.content = x;
auto temp = allocator.make!(Entry!T)(x);
if (empty)
{
first.next = rear = temp;
@ -99,29 +183,36 @@ class Queue(T)
rear.next = temp;
rear = rear.next;
}
return this;
}
/// Ditto.
alias insert = insertBack;
ref typeof(this) enqueue(T x)
{
return enqueue(x);
}
deprecated("Use enqueue instead.")
alias insert = enqueue;
deprecated("Use enqueue instead.")
alias insertBack = enqueue;
///
unittest
{
auto q = make!(Queue!int)(theAllocator);
Queue!int q;
assert(q.empty);
q.insertBack(8);
assert(q.front == 8);
q.insertBack(9);
assert(q.front == 8);
dispose(theAllocator, q);
q.enqueue(8).enqueue(9);
assert(q.dequeue() == 8);
assert(q.dequeue() == 9);
}
/**
* Returns: $(D_KEYWORD true) if the queue is empty.
*/
@property bool empty() inout const
@property bool empty() const
{
return first.next is null;
}
@ -129,79 +220,82 @@ class Queue(T)
///
unittest
{
auto q = make!(Queue!int)(theAllocator);
Queue!int q;
int value = 7;
assert(q.empty);
q.insertBack(value);
q.enqueue(value);
assert(!q.empty);
dispose(theAllocator, q);
}
/**
* Move the position to the next element.
*
* Returns: Dequeued element.
*/
void popFront()
T dequeue()
in
{
assert(!empty);
assert(allocator !is null);
}
body
{
auto n = first.next.next;
T ret = move(first.next.content);
dispose(allocator, first.next);
first.next = n;
return ret;
}
deprecated("Use dequeue instead.")
alias popFront = dequeue;
///
unittest
{
auto q = make!(Queue!int)(theAllocator);
Queue!int q;
q.insertBack(8);
q.insertBack(9);
assert(q.front == 8);
q.popFront();
assert(q.front == 9);
dispose(theAllocator, q);
q.enqueue(8).enqueue(9);
assert(q.dequeue() == 8);
assert(q.dequeue() == 9);
}
/**
* $(D_KEYWORD foreach) iteration.
* $(D_KEYWORD foreach) iteration. The elements will be automatically
* dequeued.
*
* Params:
* dg = $(D_KEYWORD foreach) body.
*/
int opApply(scope int delegate(ref size_t i, ref T) dg)
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
{
int result;
for (size_t i = 0; !empty; ++i)
{
if ((result = dg(i, front)) != 0)
auto e = dequeue();
if ((result = dg(i, e)) != 0)
{
return result;
}
popFront();
}
return result;
}
/// Ditto.
int opApply(scope int delegate(ref T) dg)
int opApply(scope int delegate(ref T) @nogc dg)
{
int result;
while (!empty)
{
if ((result = dg(front)) != 0)
auto e = dequeue();
if ((result = dg(e)) != 0)
{
return result;
}
popFront();
}
return result;
}
@ -209,12 +303,10 @@ class Queue(T)
///
unittest
{
auto q = theAllocator.make!(Queue!int);
Queue!int q;
size_t j;
q.insertBack(5);
q.insertBack(4);
q.insertBack(9);
q.enqueue(5).enqueue(4).enqueue(9);
foreach (i, e; q)
{
assert(i != 2 || e == 9);
@ -226,9 +318,7 @@ class Queue(T)
assert(q.empty);
j = 0;
q.insertBack(5);
q.insertBack(4);
q.insertBack(9);
q.enqueue(5).enqueue(4).enqueue(9);
foreach (e; q)
{
assert(j != 2 || e == 9);
@ -238,48 +328,28 @@ class Queue(T)
}
assert(j == 3);
assert(q.empty);
dispose(theAllocator, q);
}
/**
* Queue entry.
*/
protected struct Entry
{
/// Queue item content.
T content;
/// Next list item.
Entry* next;
}
/// The first element of the list.
protected Entry first;
private Entry!T first;
/// The last element of the list.
protected Entry* rear;
private Entry!T* rear;
/// The allocator.
protected IAllocator allocator;
mixin DefaultAllocator;
}
///
unittest
{
auto q = theAllocator.make!(Queue!int);
Queue!int q;
q.insertBack(5);
q.enqueue(5);
assert(!q.empty);
q.insertBack(4);
assert(q.front == 5);
q.enqueue(4).enqueue(9);
q.insertBack(9);
assert(q.front == 5);
q.popFront();
assert(q.front == 4);
assert(q.dequeue() == 5);
foreach (i, ref e; q)
{
@ -287,6 +357,4 @@ unittest
assert(i != 1 || e == 9);
}
assert(q.empty);
theAllocator.dispose(q);
}

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
*/
module tanya.container.bit;
module tanya.crypto.bit;
/**
* Wrapper that allows bit manipulation on $(D_KEYWORD ubyte[]) array.

View File

@ -10,7 +10,7 @@
*/
module tanya.crypto.des;
import tanya.container.bit;
import tanya.crypto.bit;
import tanya.crypto.symmetric;
/// Initial permutation table.

View File

@ -44,7 +44,7 @@ enum PaddingMode
ubyte[] pad(ref ubyte[] input,
in PaddingMode mode,
in ushort blockSize,
IAllocator allocator = theAllocator)
shared Allocator allocator = defaultAllocator)
in
{
assert(blockSize > 0 && blockSize <= 256);
@ -60,21 +60,21 @@ body
final switch (mode) with (PaddingMode)
{
case zero:
allocator.expandArray(input, needed);
allocator.resizeArray(input, input.length + needed);
break;
case pkcs7:
if (needed)
{
allocator.expandArray(input, needed);
allocator.resizeArray(input, input.length + needed);
input[input.length - needed ..$].each!((ref e) => e = needed);
}
else
{
allocator.expandArray(input, blockSize);
allocator.resizeArray(input, input.length + blockSize);
}
break;
case ansiX923:
allocator.expandArray(input, needed ? needed : blockSize);
allocator.resizeArray(input, input.length + (needed ? needed : blockSize));
input[$ - 1] = needed;
break;
}
@ -86,7 +86,7 @@ body
unittest
{
{ // Zeros
auto input = theAllocator.makeArray!ubyte(50);
auto input = defaultAllocator.makeArray!ubyte(50);
pad(input, PaddingMode.zero, 64);
assert(input.length == 64);
@ -95,10 +95,10 @@ unittest
assert(input.length == 64);
assert(input[63] == 0);
theAllocator.dispose(input);
defaultAllocator.dispose(input);
}
{ // PKCS#7
auto input = theAllocator.makeArray!ubyte(50);
auto input = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i)
{
input[i] = i;
@ -140,10 +140,10 @@ unittest
}
}
theAllocator.dispose(input);
defaultAllocator.dispose(input);
}
{ // ANSI X.923
auto input = theAllocator.makeArray!ubyte(50);
auto input = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i)
{
input[i] = i;
@ -185,7 +185,7 @@ unittest
}
}
theAllocator.dispose(input);
defaultAllocator.dispose(input);
}
}
@ -204,7 +204,7 @@ unittest
ref ubyte[] unpad(ref ubyte[] input,
in PaddingMode mode,
in ushort blockSize,
IAllocator allocator = theAllocator)
shared Allocator allocator = defaultAllocator)
in
{
assert(input.length != 0);
@ -220,7 +220,7 @@ body
case ansiX923:
immutable last = input[$ - 1];
allocator.shrinkArray(input, last ? last : blockSize);
allocator.resizeArray(input, input.length - (last ? last : blockSize));
break;
}
@ -231,8 +231,8 @@ body
unittest
{
{ // Zeros
auto input = theAllocator.makeArray!ubyte(50);
auto inputDup = theAllocator.makeArray!ubyte(50);
auto input = defaultAllocator.makeArray!ubyte(50);
auto inputDup = defaultAllocator.makeArray!ubyte(50);
pad(input, PaddingMode.zero, 64);
pad(inputDup, PaddingMode.zero, 64);
@ -240,13 +240,13 @@ unittest
unpad(input, PaddingMode.zero, 64);
assert(input == inputDup);
theAllocator.dispose(input);
theAllocator.dispose(inputDup);
defaultAllocator.dispose(input);
defaultAllocator.dispose(inputDup);
}
{ // PKCS#7
auto input = theAllocator.makeArray!ubyte(50);
auto inputDup = theAllocator.makeArray!ubyte(50);
auto input = defaultAllocator.makeArray!ubyte(50);
auto inputDup = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i)
{
input[i] = i;
@ -257,12 +257,12 @@ unittest
unpad(input, PaddingMode.pkcs7, 64);
assert(input == inputDup);
theAllocator.dispose(input);
theAllocator.dispose(inputDup);
defaultAllocator.dispose(input);
defaultAllocator.dispose(inputDup);
}
{ // ANSI X.923
auto input = theAllocator.makeArray!ubyte(50);
auto inputDup = theAllocator.makeArray!ubyte(50);
auto input = defaultAllocator.makeArray!ubyte(50);
auto inputDup = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i)
{
input[i] = i;
@ -273,7 +273,7 @@ unittest
unpad(input, PaddingMode.pkcs7, 64);
assert(input == inputDup);
theAllocator.dispose(input);
theAllocator.dispose(inputDup);
defaultAllocator.dispose(input);
defaultAllocator.dispose(inputDup);
}
}

View File

@ -10,9 +10,7 @@
*/
module tanya.crypto;
public
{
import tanya.crypto.des;
import tanya.crypto.mode;
import tanya.crypto.symmetric;
}
public import tanya.crypto.bit;
public import tanya.crypto.des;
public import tanya.crypto.mode;
public import tanya.crypto.symmetric;

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,9 @@
*/
module tanya.math;
import std.traits;
public import tanya.math.mp;
public import tanya.math.random;
version (unittest)
{
@ -20,26 +22,32 @@ version (unittest)
/**
* 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)
*/
ulong pow(ulong x, ulong y, ulong z) nothrow pure @safe @nogc
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);
}
out (result)
{
assert(result >= 0);
assert(z > 0, "Division by zero.");
}
body
{
ulong mask = ulong.max / 2 + 1, result;
G mask = G.max / 2 + 1;
H result;
if (y == 0)
{
@ -51,7 +59,7 @@ body
}
do
{
auto bit = y & mask;
immutable bit = y & mask;
if (!result && bit)
{
result = x;
@ -64,15 +72,53 @@ body
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;
}
///
unittest
pure nothrow @safe @nogc unittest
{
assert(pow(3, 5, 7) == 5);
assert(pow(2, 2, 1) == 0);
@ -85,6 +131,20 @@ unittest
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.
*

View File

@ -3,16 +3,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Random number generator.
*
* 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.random;
module tanya.math.random;
import tanya.memory;
import std.digest.sha;
import std.typecons;
import tanya.memory;
/// Block size of entropy accumulator (SHA-512).
enum blockSize = 64;
@ -148,13 +150,13 @@ version (linux)
/**
* Pseudorandom number generator.
* ---
* auto entropy = theAllocator.make!Entropy;
* auto entropy = defaultAllocator.make!Entropy();
*
* ubyte[blockSize] output;
*
* output = entropy.random;
*
* theAllocator.finalize(entropy);
* defaultAllocator.finalize(entropy);
* ---
*/
class Entropy
@ -164,7 +166,7 @@ class Entropy
private ubyte sourceCount_;
private IAllocator allocator;
private shared Allocator allocator;
/// Entropy accumulator.
protected SHA!(maxGather * 8, 512) accumulator;
@ -175,7 +177,7 @@ class Entropy
* allocator = Allocator to allocate entropy sources available on the
* system.
*/
this(size_t maxSources = 20, IAllocator allocator = theAllocator)
this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
in
{
assert(maxSources > 0 && maxSources <= ubyte.max);

View File

@ -10,173 +10,118 @@
*/
module tanya.memory.allocator;
import std.experimental.allocator;
import std.traits;
import std.typecons;
/**
* Abstract class implementing a basic allocator.
*/
abstract class Allocator : IAllocator
interface Allocator
{
/**
* Not supported.
*
* Returns: $(D_KEYWORD false).
* Returns: Alignment offered.
*/
bool deallocateAll() const @nogc @safe pure nothrow
{
return false;
}
@property uint alignment() const shared pure nothrow @safe @nogc;
/**
* Not supported.
* Allocates $(D_PARAM size) bytes of memory.
*
* Returns $(D_PSYMBOL Ternary.unknown).
*/
Ternary empty() const @nogc @safe pure nothrow
{
return Ternary.unknown;
}
/**
* Not supported.
*
* Params:
* b = Memory block.
*
* Returns: $(D_PSYMBOL Ternary.unknown).
*/
Ternary owns(void[] b) const @nogc @safe pure nothrow
{
return Ternary.unknown;
}
/**
* Not supported.
*
* Params:
* p = Pointer to a memory block.
* result = Full block allocated.
*
* Returns: $(D_PSYMBOL Ternary.unknown).
*/
Ternary resolveInternalPointer(void* p, ref void[] result)
const @nogc @safe pure nothrow
{
return Ternary.unknown;
}
/**
* Params:
* size = Amount of memory to allocate.
*
* Returns: The good allocation size that guarantees zero internal
* fragmentation.
* Returns: Pointer to the new allocated memory.
*/
size_t goodAllocSize(size_t s)
{
auto rem = s % alignment;
return rem ? s + alignment - rem : s;
}
void[] allocate(in size_t size) shared nothrow @nogc;
/**
* Not supported.
*
* Returns: $(D_KEYWORD null).
*
*/
void[] allocateAll() const @nogc @safe pure nothrow
{
return null;
}
/**
* Not supported.
* Deallocates a memory block.
*
* Params:
* b = Block to be expanded.
* s = New size.
* p = A pointer to the memory block to be freed.
*
* Returns: $(D_KEYWORD false).
* Returns: Whether the deallocation was successful.
*/
bool expand(ref void[] b, size_t s) const @nogc @safe pure nothrow
{
return false;
bool deallocate(void[] p) shared nothrow @nogc;
/**
* Increases or decreases the size of a memory block.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: Pointer to the allocated memory.
*/
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc;
/**
* Expands a memory block in place.
*
* 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 expand(ref void[] p, in size_t size) shared nothrow @nogc;
}
/**
* Not supported.
*
* Params:
* n = Amount of memory to allocate.
* a = Alignment.
*
* Returns: $(D_KEYWORD null).
* 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).
*/
void[] alignedAllocate(size_t n, uint a) const @nogc @safe pure nothrow
mixin template DefaultAllocator()
{
return null;
}
/**
* Not supported.
*
* Params:
* n = Amount of memory to allocate.
* a = Alignment.
*
* Returns: $(D_KEYWORD false).
*/
bool alignedReallocate(ref void[] b, size_t size, uint alignment)
const @nogc @safe pure nothrow
{
return false;
}
}
/// Allocator.
protected shared Allocator allocator_;
/**
* Params:
* T = Element type of the array being created.
* 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
* allocator = The allocator should be used.
*/
bool resizeArray(T)(IAllocator allocator,
ref T[] array,
in size_t length)
this(shared Allocator allocator)
in
{
void[] buf = array;
if (!allocator.reallocate(buf, length * T.sizeof))
{
return false;
assert(allocator !is null);
}
array = cast(T[]) buf;
return true;
body
{
this.allocator_ = allocator;
}
///
unittest
/**
* 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)
{
int[] p;
theAllocator.resizeArray(p, 20);
assert(p.length == 20);
theAllocator.resizeArray(p, 30);
assert(p.length == 30);
theAllocator.resizeArray(p, 10);
assert(p.length == 10);
theAllocator.resizeArray(p, 0);
assert(p is null);
assert(allocator !is null);
}
body
{
if (allocator_ is null)
{
allocator_ = defaultAllocator;
}
return allocator_;
}
enum bool isFinalizable(T) = is(T == class) || is(T == interface)
|| hasElaborateDestructor!T || isDynamicArray!T;
/// 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_;
}
}

View File

@ -10,9 +10,9 @@
*/
module tanya.memory.mmappool;
import core.stdc.string;
import std.algorithm.comparison;
import tanya.memory.allocator;
import core.atomic;
import core.exception;
version (Posix)
{
@ -27,7 +27,7 @@ else version (Windows)
}
/**
* This allocator allocates memory in regions (multiple of 4 KB for example).
* 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.
@ -36,7 +36,7 @@ else version (Windows)
* block as free and only if all blocks in the region are free, the complete
* region is deallocated.
*
* ----------------------------------------------------------------------------
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* | | | | | || | | |
* | |prev <----------- | || | | |
* | R | B | | B | || R | B | |
@ -46,44 +46,19 @@ else version (Windows)
* | O | K | | K | prev O | K | |
* | N | -----------> next| || N | | |
* | | | | | || | | |
* --------------------------------------------------- ------------------------
*
* TODO:
* $(UL
* $(LI Thread safety (core.atomic.cas))
* $(LI If two neighbour blocks are free, they can be merged)
* $(LI Reallocation shoud check if there is enough free space in the
* next block instead of always moving the memory)
* $(LI Make 64 KB regions mininmal region size on Linux)
* )
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
class MmapPool : Allocator
final class MmapPool : Allocator
{
@disable this();
shared static this()
{
version (Posix)
{
pageSize = sysconf(_SC_PAGE_SIZE);
}
else version (Windows)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
pageSize = si.dwPageSize;
}
}
/**
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
* 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 size, TypeInfo ti = null) @nogc @trusted nothrow
void[] allocate(in size_t size) shared nothrow @nogc
{
if (!size)
{
@ -101,7 +76,7 @@ class MmapPool : Allocator
}
///
@nogc @safe nothrow unittest
nothrow unittest
{
auto p = MmapPool.instance.allocate(20);
@ -115,16 +90,16 @@ class MmapPool : Allocator
* into two blocks if the block is too large.
*
* Params:
* size = Minimum size the block should have.
* size = Minimum size the block should have (aligned).
*
* Returns: Data the block points to or $(D_KEYWORD null).
*/
private void* findBlock(size_t size) @nogc nothrow
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 + regionEntrySize);
block1 = cast(Block) (cast(void*) r + RegionEntry.sizeof);
do
{
if (block1.free && block1.size >= size)
@ -138,35 +113,37 @@ class MmapPool : Allocator
{
return null;
}
else if (block1.size >= size + alignment + blockEntrySize)
else if (block1.size >= size + alignment_ + BlockEntry.sizeof)
{ // Split the block if needed
Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size);
Block block2 = cast(Block) (cast(void*) block1 + BlockEntry.sizeof + size);
block2.prev = block1;
if (block1.next is null)
block2.next = block1.next;
block2.free = true;
block2.size = block1.size - BlockEntry.sizeof - size;
block2.region = block1.region;
if (block1.next !is null)
{
block2.next = null;
}
else
{
block2.next = block1.next.next;
block1.next.prev = block2;
}
block1.next = block2;
block1.free = false;
block2.free = true;
block2.size = block1.size - blockEntrySize - size;
block1.size = size;
block2.region = block1.region;
atomicOp!"+="(block1.region.blocks, 1);
}
else
{
block1.free = false;
atomicOp!"+="(block1.region.blocks, 1);
block1.region.blocks = block1.region.blocks + 1;
return cast(void*) block1 + BlockEntry.sizeof;
}
return cast(void*) block1 + blockEntrySize;
// 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;
}
/**
@ -177,14 +154,14 @@ class MmapPool : Allocator
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) @nogc @trusted nothrow
bool deallocate(void[] p) shared nothrow @nogc
{
if (p is null)
{
return true;
}
Block block = cast(Block) (p.ptr - blockEntrySize);
Block block = cast(Block) (p.ptr - BlockEntry.sizeof);
if (block.region.blocks <= 1)
{
if (block.region.prev !is null)
@ -208,22 +185,129 @@ class MmapPool : Allocator
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;
atomicOp!"-="(block.region.blocks, 1);
return true;
}
block.region.blocks = block.region.blocks - 1;
return true;
}
///
@nogc @safe nothrow unittest
nothrow unittest
{
auto p = MmapPool.instance.allocate(20);
assert(MmapPool.instance.deallocate(p));
}
/**
* Expands a memory block in place.
*
* 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 expand(ref void[] p, in size_t size) shared nothrow @nogc
{
if (size <= p.length)
{
return true;
}
if (p is null)
{
return false;
}
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 - 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_)
{
// We should move the start position of the next block. The order may be
// important because the old block and the new one can overlap.
auto block2 = cast(Block) (p.ptr + dataSize);
block2.size = block1.next.size - delta;
block2.free = true;
block2.region = block1.region;
block2.next = block1.next.next;
block2.prev = block1;
block1.size = block1.size + delta;
if (block1.next.next !is null)
{
block1.next.next.prev = block2;
}
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.expand(p, 5));
assert(p is null);
p = MmapPool.instance.allocate(1);
auto orig = p.ptr;
assert(MmapPool.instance.expand(p, 2));
assert(p.length == 2);
assert(p.ptr == orig);
assert(MmapPool.instance.expand(p, 4));
assert(p.length == 4);
assert(p.ptr == orig);
assert(MmapPool.instance.expand(p, 2));
assert(p.length == 4);
assert(p.ptr == orig);
MmapPool.instance.deallocate(p);
}
/**
* Increases or decreases the size of a memory block.
*
@ -233,33 +317,36 @@ class MmapPool : Allocator
*
* Returns: Whether the reallocation was successful.
*/
bool reallocate(ref void[] p, size_t size) @nogc @trusted nothrow
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc
{
void[] reallocP;
if (size == p.length)
if (size == 0)
{
if (deallocate(p))
{
p = null;
return true;
}
return false;
}
else if (size <= p.length)
{
// Leave the block as is.
p = p.ptr[0 .. size];
return true;
}
else if (expand(p, size))
{
return true;
}
else if (size > 0)
{
reallocP = allocate(size);
// Can't extend, allocate a new block, copy and delete the previous.
void[] reallocP = allocate(size);
if (reallocP is null)
{
return false;
}
}
if (p !is null)
{
if (size > p.length)
{
reallocP[0..p.length] = p[0..$];
}
else if (size > 0)
{
reallocP[0..size] = p[0..size];
}
memcpy(reallocP.ptr, p.ptr, min(p.length, size));
deallocate(p);
}
p = reallocP;
@ -268,7 +355,7 @@ class MmapPool : Allocator
}
///
@nogc nothrow unittest
nothrow unittest
{
void[] p;
MmapPool.instance.reallocate(p, 10 * int.sizeof);
@ -301,18 +388,34 @@ class MmapPool : Allocator
*
* Returns: Global $(D_PSYMBOL MmapPool) instance.
*/
static @property ref MmapPool instance() @nogc @trusted nothrow
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)
{
data[0..instanceSize] = typeid(MmapPool).initializer[];
instance_ = cast(MmapPool) data;
memcpy(data, typeid(MmapPool).initializer.ptr, instanceSize);
instance_ = cast(shared MmapPool) data;
instance_.head = head;
}
}
@ -320,12 +423,12 @@ class MmapPool : Allocator
}
///
@nogc @safe nothrow unittest
nothrow unittest
{
assert(instance is instance);
}
/**
/*
* Initializes a region for one element.
*
* Params:
@ -334,8 +437,8 @@ class MmapPool : Allocator
*
* Returns: A pointer to the data.
*/
private static void* initializeRegion(size_t size,
ref Region head) @nogc nothrow
private static void* initializeRegion(size_t size, ref Region head)
nothrow @nogc
{
immutable regionSize = calculateRegionSize(size);
@ -349,10 +452,6 @@ class MmapPool : Allocator
0);
if (p is MAP_FAILED)
{
if (errno == ENOMEM)
{
onOutOfMemoryError();
}
return null;
}
}
@ -364,10 +463,6 @@ class MmapPool : Allocator
PAGE_READWRITE);
if (p is null)
{
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY)
{
onOutOfMemoryError();
}
return null;
}
}
@ -386,13 +481,13 @@ class MmapPool : Allocator
head = region;
// Initialize the data block
void* memoryPointer = p + regionEntrySize;
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 + blockEntrySize;
void* data = memoryPointer + BlockEntry.sizeof;
// Free block after data
memoryPointer = data + size;
@ -400,28 +495,26 @@ class MmapPool : Allocator
block1.prev = block2.next = null;
block1.next = block2;
block2.prev = block1;
block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2;
block2.size = regionSize - size - RegionEntry.sizeof - BlockEntry.sizeof * 2;
block2.free = true;
block1.region = block2.region = region;
return data;
}
/// Ditto.
private void* initializeRegion(size_t size) @nogc nothrow
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).
*/
pragma(inline)
private static immutable(size_t) addAlignment(size_t x)
@nogc @safe pure nothrow
pure nothrow @safe @nogc
out (result)
{
assert(result > 0);
@ -431,34 +524,35 @@ class MmapPool : Allocator
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)
@nogc @safe pure nothrow
nothrow @safe @nogc
out (result)
{
assert(result > 0);
}
body
{
x += regionEntrySize + blockEntrySize * 2;
x += RegionEntry.sizeof + BlockEntry.sizeof * 2;
return x / pageSize * pageSize + pageSize;
}
@property uint alignment() const @nogc @safe pure nothrow
/**
* Returns: Alignment offered.
*/
@property uint alignment() shared const pure nothrow @safe @nogc
{
return alignment_;
}
private enum alignment_ = 8;
private static MmapPool instance_;
private shared static immutable size_t pageSize;
private shared static MmapPool instance_;
private shared static size_t pageSize;
private shared struct RegionEntry
{
@ -468,18 +562,15 @@ class MmapPool : Allocator
size_t size;
}
private alias Region = shared RegionEntry*;
private enum regionEntrySize = 32;
private shared Region head;
private shared struct BlockEntry
{
Block prev;
Block next;
bool free;
size_t size;
Region region;
size_t size;
bool free;
}
private alias Block = shared BlockEntry*;
private enum blockEntrySize = 40;
}

View File

@ -10,5 +10,225 @@
*/
module tanya.memory;
import core.exception;
public import std.experimental.allocator : make, makeArray;
import std.traits;
public import tanya.memory.allocator;
public import std.experimental.allocator;
public import tanya.memory.types;
// From druntime
private extern (C) void _d_monitordelete(Object h, bool det) nothrow @nogc;
shared Allocator allocator;
shared static this() nothrow @trusted @nogc
{
import tanya.memory.mmappool;
allocator = MmapPool.instance;
}
@property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc
{
return allocator;
}
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
{
.allocator = allocator;
}
/**
* Returns the size in bytes of the state that needs to be allocated to hold an
* object of type $(D_PARAM T).
*
* Params:
* T = Object type.
*/
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 (p is null)
{
return;
}
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;
}
else
{
alias ob = p;
}
auto ptr = cast(void *) ob;
auto support = ptr[0 .. typeid(ob).initializer.length];
scope (success)
{
() @trusted { allocator.deallocate(support); }();
p = null;
}
auto ppv = cast(void**) ptr;
if (!*ppv)
{
return;
}
auto pc = cast(ClassInfo*) *ppv;
scope (exit)
{
*ppv = null;
}
auto c = *pc;
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)
{
(cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
}
}
while ((c = c.base) !is null);
if (ppv[1]) // if monitor is not null
{
_d_monitordelete(cast(Object) ptr, true);
}
}
/// Ditto.
void dispose(T)(shared Allocator allocator, auto ref T[] array)
{
static if (hasElaborateDestructor!(typeof(array[0])))
{
foreach (ref e; array)
{
destroy(e);
}
}
() @trusted { allocator.deallocate(array); }();
array = null;
}

458
source/tanya/memory/types.d Normal file
View 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);
}
}

49
source/tanya/meta/gen.d Normal file
View File

@ -0,0 +1,49 @@
/* 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/. */
/**
* Templates that generate values.
*
* 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.meta.gen;
import std.traits;
/**
* Initializer list.
*
* Generates a static array with elements from $(D_PARAM args). All elements
* should have the same type. It can be used in constructors which accept a
* list of the elements of the same type in the situations where variadic
* functions and templates can't be used.
*
* Params:
* Args = Argument type.
* args = Arguments.
*/
enum IL(Args...)(Args args)
if (Args.length > 0)
{
alias BaseType = typeof(args[0]);
BaseType[args.length] result;
foreach (i, a; args)
{
static assert(isImplicitlyConvertible!(typeof(a), BaseType));
result[i] = a;
}
return result;
}
///
unittest
{
static assert(IL(1, 5, 8).length == 3);
static assert(IL(1, 5, 8).sizeof == 3 * int.sizeof);
}

View File

@ -0,0 +1,15 @@
/* 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/. */
/**
* Metaprogramming.
*
* 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.meta;
public import tanya.meta.gen;

View File

@ -6,7 +6,7 @@
* 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)
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.network.socket;
@ -15,9 +15,8 @@ import core.stdc.errno;
import core.time;
import std.algorithm.comparison;
import std.algorithm.searching;
public import std.socket : AddressException, socket_t, Linger, SocketOptionLevel,
SocketType, AddressFamily, AddressInfo,
SocketOption;
public import std.socket : socket_t, Linger, SocketOptionLevel, SocketOption,
SocketType, AddressFamily, AddressInfo;
import std.traits;
import std.typecons;
@ -154,6 +153,23 @@ else version (Windows)
DWORD lpFlags,
LPOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
int WSAIoctl(SOCKET s,
uint dwIoControlCode,
void* lpvInBuffer,
uint cbInBuffer,
void* lpvOutBuffer,
uint cbOutBuffer,
uint* lpcbBytesReturned,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
alias LPFN_ACCEPTEX = BOOL function(SOCKET,
SOCKET,
PVOID,
DWORD,
DWORD,
DWORD,
LPDWORD,
LPOVERLAPPED);
}
alias WSASocket = WSASocketW;
@ -213,7 +229,7 @@ else version (Windows)
* handle = Socket handle.
* af = Address family.
*/
this(socket_t handle, AddressFamily af)
this(socket_t handle, AddressFamily af) @nogc
{
super(handle, af);
}
@ -233,7 +249,7 @@ else version (Windows)
*/
bool beginReceive(ubyte[] buffer,
SocketState overlapped,
Flags flags = Flags(Flag.none)) @trusted
Flags flags = Flags(Flag.none)) @nogc @trusted
{
auto receiveFlags = cast(DWORD) flags;
@ -252,7 +268,7 @@ else version (Windows)
if (result == SOCKET_ERROR && !wouldHaveBlocked)
{
throw theAllocator.make!SocketException("Unable to receive");
throw defaultAllocator.make!SocketException("Unable to receive");
}
return result == 0;
}
@ -267,7 +283,7 @@ else version (Windows)
*
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
*/
int endReceive(SocketState overlapped) @trusted
int endReceive(SocketState overlapped) @nogc @trusted
out (count)
{
assert(count >= 0);
@ -282,7 +298,7 @@ else version (Windows)
if (result == FALSE && !wouldHaveBlocked)
{
disconnected_ = true;
throw theAllocator.make!SocketException("Unable to receive");
throw defaultAllocator.make!SocketException("Unable to receive");
}
if (lpNumber == 0)
{
@ -306,7 +322,7 @@ else version (Windows)
*/
bool beginSend(ubyte[] buffer,
SocketState overlapped,
Flags flags = Flags(Flag.none)) @trusted
Flags flags = Flags(Flag.none)) @nogc @trusted
{
overlapped.handle = cast(HANDLE) handle_;
overlapped.event = OverlappedSocketEvent.write;
@ -324,7 +340,7 @@ else version (Windows)
if (result == SOCKET_ERROR && !wouldHaveBlocked)
{
disconnected_ = true;
throw theAllocator.make!SocketException("Unable to send");
throw defaultAllocator.make!SocketException("Unable to send");
}
return result == 0;
}
@ -339,7 +355,7 @@ else version (Windows)
*
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
*/
int endSend(SocketState overlapped) @trusted
int endSend(SocketState overlapped) @nogc @trusted
out (count)
{
assert(count >= 0);
@ -354,7 +370,7 @@ else version (Windows)
if (result == FALSE && !wouldHaveBlocked)
{
disconnected_ = true;
throw theAllocator.make!SocketException("Unable to receive");
throw defaultAllocator.make!SocketException("Unable to receive");
}
return lpNumber;
}
@ -373,7 +389,7 @@ else version (Windows)
*
* Throws: $(D_PSYMBOL SocketException) on errors.
*/
this(AddressFamily af) @trusted
this(AddressFamily af) @nogc @trusted
{
super(af);
scope (failure)
@ -396,7 +412,8 @@ else version (Windows)
NULL);
if (!result == SOCKET_ERROR)
{
throw theAllocator.make!SocketException("Unable to retrieve an accept extension function pointer");
throw make!SocketException(defaultAllocator,
"Unable to retrieve an accept extension function pointer");
}
}
@ -411,12 +428,12 @@ else version (Windows)
*
* Throws: $(D_PSYMBOL SocketException) on accept errors.
*/
bool beginAccept(SocketState overlapped) @trusted
bool beginAccept(SocketState overlapped) @nogc @trusted
{
auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0);
if (socket == socket_t.init)
{
throw theAllocator.make!SocketException("Unable to create socket");
throw defaultAllocator.make!SocketException("Unable to create socket");
}
scope (failure)
{
@ -425,8 +442,10 @@ else version (Windows)
DWORD dwBytes;
overlapped.handle = cast(HANDLE) socket;
overlapped.event = OverlappedSocketEvent.accept;
overlapped.buffer.len = (sockaddr_in.sizeof + 16) * 2;
overlapped.buffer.buf = theAllocator.makeArray!char(overlapped.buffer.len).ptr;
immutable len = (sockaddr_in.sizeof + 16) * 2;
overlapped.buffer.len = len;
overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr;
// We don't want to get any data now, but only start to accept the connections
BOOL result = acceptExtension(handle_,
@ -439,7 +458,7 @@ else version (Windows)
&overlapped.overlapped);
if (result == FALSE && !wouldHaveBlocked)
{
throw theAllocator.make!SocketException("Unable to accept socket connection");
throw defaultAllocator.make!SocketException("Unable to accept socket connection");
}
return result == TRUE;
}
@ -455,17 +474,18 @@ else version (Windows)
*
* Throws: $(D_PSYMBOL SocketException) if unable to accept.
*/
OverlappedConnectedSocket endAccept(SocketState overlapped) @trusted
OverlappedConnectedSocket endAccept(SocketState overlapped) @nogc @trusted
{
scope (exit)
{
theAllocator.dispose(overlapped.buffer.buf[0..overlapped.buffer.len]);
defaultAllocator.dispose(overlapped.buffer.buf[0..overlapped.buffer.len]);
}
auto socket = theAllocator.make!OverlappedConnectedSocket(cast(socket_t) overlapped.handle,
auto socket = make!OverlappedConnectedSocket(defaultAllocator,
cast(socket_t) overlapped.handle,
addressFamily);
scope (failure)
{
theAllocator.dispose(socket);
defaultAllocator.dispose(socket);
}
socket.setOption(SocketOptionLevel.SOCKET,
cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
@ -663,7 +683,7 @@ abstract class Socket
/// Address family.
protected AddressFamily family;
private @property void handle(socket_t handle)
private @property void handle(socket_t handle) @nogc
in
{
assert(handle != socket_t.init);
@ -693,7 +713,7 @@ abstract class Socket
* handle = Socket.
* af = Address family.
*/
this(socket_t handle, AddressFamily af)
this(socket_t handle, AddressFamily af) @nogc
in
{
assert(handle != socket_t.init);
@ -728,7 +748,9 @@ abstract class Socket
*
* Throws: $(D_PSYMBOL SocketException) on error.
*/
protected int getOption(SocketOptionLevel level, SocketOption option, void[] result) const @trusted
protected int getOption(SocketOptionLevel level,
SocketOption option,
void[] result) const @trusted @nogc
{
auto length = cast(socklen_t) result.length;
if (getsockopt(handle_,
@ -737,25 +759,31 @@ abstract class Socket
result.ptr,
&length) == SOCKET_ERROR)
{
throw theAllocator.make!SocketException("Unable to get socket option");
throw defaultAllocator.make!SocketException("Unable to get socket option");
}
return length;
}
/// Ditto.
int getOption(SocketOptionLevel level, SocketOption option, out size_t result) const @trusted
int getOption(SocketOptionLevel level,
SocketOption option,
out size_t result) const @trusted @nogc
{
return getOption(level, option, (&result)[0..1]);
}
/// Ditto.
int getOption(SocketOptionLevel level, SocketOption option, out Linger result) const @trusted
int getOption(SocketOptionLevel level,
SocketOption option,
out Linger result) const @trusted @nogc
{
return getOption(level, option, (&result.clinger)[0..1]);
}
/// Ditto.
int getOption(SocketOptionLevel level, SocketOption option, out Duration result) const @trusted
int getOption(SocketOptionLevel level,
SocketOption option,
out Duration result) const @trusted @nogc
{
// WinSock returns the timeout values as a milliseconds DWORD,
// while Linux and BSD return a timeval struct.
@ -784,12 +812,13 @@ abstract class Socket
* Params:
* level = Protocol level at that the option exists.
* option = Option.
* result = Option value.
* value = Option value.
*
* Throws: $(D_PSYMBOL SocketException) on error.
*/
protected void setOption(SocketOptionLevel level, SocketOption option, void[] value)
const @trusted
protected void setOption(SocketOptionLevel level,
SocketOption option,
void[] value) const @trusted @nogc
{
if (setsockopt(handle_,
cast(int)level,
@ -797,24 +826,27 @@ abstract class Socket
value.ptr,
cast(uint) value.length) == SOCKET_ERROR)
{
throw theAllocator.make!SocketException("Unable to set socket option");
throw defaultAllocator.make!SocketException("Unable to set socket option");
}
}
/// Ditto.
void setOption(SocketOptionLevel level, SocketOption option, size_t value) const @trusted
void setOption(SocketOptionLevel level, SocketOption option, size_t value)
const @trusted @nogc
{
setOption(level, option, (&value)[0..1]);
}
/// Ditto.
void setOption(SocketOptionLevel level, SocketOption option, Linger value) const @trusted
void setOption(SocketOptionLevel level, SocketOption option, Linger value)
const @trusted @nogc
{
setOption(level, option, (&value.clinger)[0..1]);
}
/// Ditto.
void setOption(SocketOptionLevel level, SocketOption option, Duration value) const @trusted
void setOption(SocketOptionLevel level, SocketOption option, Duration value)
const @trusted @nogc
{
version (Posix)
{
@ -852,7 +884,7 @@ abstract class Socket
* Params:
* yes = Socket's blocking flag.
*/
@property void blocking(bool yes)
@property void blocking(bool yes) @nogc
{
version (Posix)
{
@ -865,7 +897,8 @@ abstract class Socket
}
if (fl == SOCKET_ERROR)
{
throw theAllocator.make!SocketException("Unable to set socket blocking");
throw make!SocketException(defaultAllocator,
"Unable to set socket blocking");
}
}
else version (Windows)
@ -873,7 +906,8 @@ abstract class Socket
uint num = !yes;
if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR)
{
throw theAllocator.make!SocketException("Unable to set socket blocking");
throw make!SocketException(defaultAllocator,
"Unable to set socket blocking");
}
blocking_ = yes;
}
@ -938,11 +972,11 @@ abstract class Socket
* backlog = Request of how many pending incoming connections are
* queued until $(D_PSYMBOL accept)ed.
*/
void listen(int backlog) const @trusted
void listen(int backlog) const @trusted @nogc
{
if (.listen(handle_, backlog) == SOCKET_ERROR)
{
throw theAllocator.make!SocketException("Unable to listen on socket");
throw defaultAllocator.make!SocketException("Unable to listen on socket");
}
}
@ -987,12 +1021,12 @@ class StreamSocket : Socket, ConnectionOrientedSocket
* Params:
* af = Address family.
*/
this(AddressFamily af) @trusted
this(AddressFamily af) @trusted @nogc
{
auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0);
if (handle == socket_t.init)
{
throw theAllocator.make!SocketException("Unable to create socket");
throw defaultAllocator.make!SocketException("Unable to create socket");
}
super(handle, af);
}
@ -1005,11 +1039,11 @@ class StreamSocket : Socket, ConnectionOrientedSocket
*
* Throws: $(D_PSYMBOL SocketException) if unable to bind.
*/
void bind(Address address) @trusted const
void bind(Address address) const @trusted @nogc
{
if (.bind(handle_, address.name, address.length) == SOCKET_ERROR)
{
throw theAllocator.make!SocketException("Unable to bind socket");
throw defaultAllocator.make!SocketException("Unable to bind socket");
}
}
@ -1024,7 +1058,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
*
* Throws: $(D_PSYMBOL SocketException) if unable to accept.
*/
ConnectedSocket accept() @trusted
ConnectedSocket accept() @trusted @nogc
{
socket_t sock;
@ -1048,10 +1082,11 @@ class StreamSocket : Socket, ConnectionOrientedSocket
{
return null;
}
throw theAllocator.make!SocketException("Unable to accept socket connection");
throw make!SocketException(defaultAllocator,
"Unable to accept socket connection");
}
auto newSocket = theAllocator.make!ConnectedSocket(sock, addressFamily);
auto newSocket = defaultAllocator.make!ConnectedSocket(sock, addressFamily);
version (linux)
{ // Blocking mode already set
@ -1066,7 +1101,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
}
catch (SocketException e)
{
theAllocator.dispose(newSocket);
defaultAllocator.dispose(newSocket);
throw e;
}
}
@ -1106,7 +1141,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
* handle = Socket.
* af = Address family.
*/
this(socket_t handle, AddressFamily af)
this(socket_t handle, AddressFamily af) @nogc
{
super(handle, af);
}
@ -1142,7 +1177,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
*
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
*/
ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted
ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted @nogc
{
ptrdiff_t ret;
if (!buf.length)
@ -1162,7 +1197,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
return 0;
}
disconnected_ = true;
throw theAllocator.make!SocketException("Unable to receive");
throw defaultAllocator.make!SocketException("Unable to receive");
}
return ret;
}
@ -1180,7 +1215,8 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
*
* Throws: $(D_PSYMBOL SocketException) if unable to send.
*/
ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none) const @trusted
ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none)
const @trusted @nogc
{
int sendFlags = cast(int) flags;
ptrdiff_t sent;
@ -1199,7 +1235,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
{
return 0;
}
throw theAllocator.make!SocketException("Unable to send");
throw defaultAllocator.make!SocketException("Unable to send");
}
}
@ -1238,22 +1274,23 @@ class InternetAddress : Address
anyPort = 0,
}
this(in string host, ushort port = anyPort)
this(in string host, ushort port = anyPort) @nogc
{
if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
{
throw theAllocator.make!AddressException("Address info lookup is not available on this system");
throw make!SocketException(defaultAllocator,
"Address info lookup is not available on this system");
}
addrinfo* ai_res;
port_ = port;
// Make C-string from host.
char[] node = theAllocator.makeArray!char(host.length + 1);
auto node = cast(char[]) allocator.allocate(host.length + 1);
node[0.. $ - 1] = host;
node[$ - 1] = '\0';
scope (exit)
{
theAllocator.dispose(node);
allocator.deallocate(node);
}
// Convert port to a C-string.
@ -1278,7 +1315,7 @@ class InternetAddress : Address
auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);
if (ret)
{
throw theAllocator.make!AddressException("Address info lookup failed");
throw defaultAllocator.make!SocketException("Address info lookup failed");
}
scope (exit)
{
@ -1292,7 +1329,7 @@ class InternetAddress : Address
}
if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6)
{
throw theAllocator.make!AddressException("Wrong address family");
throw defaultAllocator.make!SocketException("Wrong address family");
}
}
@ -1352,7 +1389,8 @@ bool wouldHaveBlocked() nothrow @trusted @nogc
}
else version (Windows)
{
return WSAGetLastError() == ERROR_IO_PENDING || WSAGetLastError() == EWOULDBLOCK
return WSAGetLastError() == ERROR_IO_PENDING
|| WSAGetLastError() == EWOULDBLOCK
|| WSAGetLastError() == ERROR_IO_INCOMPLETE;
}
}

View File

@ -630,32 +630,31 @@ static this()
/**
* A Unique Resource Locator.
*/
struct URL(U = string)
if (isSomeString!U)
struct URL
{
/** The URL scheme. */
U scheme;
const(char)[] scheme;
/** The username. */
U user;
const(char)[] user;
/** The password. */
U pass;
const(char)[] pass;
/** The hostname. */
U host;
const(char)[] host;
/** The port number. */
ushort port;
/** The path. */
U path;
const(char)[] path;
/** The query string. */
U query;
const(char)[] query;
/** The anchor. */
U fragment;
const(char)[] fragment;
/**
* Attempts to parse an URL from a string.
@ -666,7 +665,7 @@ struct URL(U = string)
*
* Throws: $(D_PSYMBOL URIException) if the URL is malformed.
*/
this(U source)
this(in char[] source)
{
auto value = source;
ptrdiff_t pos = -1, endPos = value.length, start;
@ -702,7 +701,7 @@ struct URL(U = string)
{
if (!parsePort(value[pos..$]))
{
throw theAllocator.make!URIException("Failed to parse port");
throw defaultAllocator.make!URIException("Failed to parse port");
}
}
goto ParsePath;
@ -751,7 +750,7 @@ struct URL(U = string)
else if (pos == 0 && parsePort(value[pos..$]))
{
// An URL shouldn't begin with a port number
throw theAllocator.make!URIException("URL begins with port");
throw defaultAllocator.make!URIException("URL begins with port");
}
else
{
@ -808,7 +807,7 @@ struct URL(U = string)
{
pass = null;
}
throw make!URIException(theAllocator,
throw make!URIException(defaultAllocator,
"Restricted characters in user information");
}
}
@ -844,7 +843,7 @@ struct URL(U = string)
{
pass = null;
}
throw theAllocator.make!URIException("Invalid port");
throw defaultAllocator.make!URIException("Invalid port");
}
break;
}
@ -866,7 +865,7 @@ struct URL(U = string)
{
pass = null;
}
throw theAllocator.make!URIException("Invalid host");
throw defaultAllocator.make!URIException("Invalid host");
}
host = value[start..pos];
@ -954,7 +953,7 @@ struct URL(U = string)
*
* Returns: Whether the port could be found.
*/
private bool parsePort(U port) pure nothrow @safe @nogc
private bool parsePort(in char[] port) pure nothrow @safe @nogc
{
ptrdiff_t i = 1;
float lPort = 0;
@ -984,14 +983,14 @@ struct URL(U = string)
///
unittest
{
auto u = URL!()("example.org");
auto u = URL("example.org");
assert(u.path == "example.org");
u = URL!()("relative/path");
u = URL("relative/path");
assert(u.path == "relative/path");
// Host and scheme
u = URL!()("https://example.org");
u = URL("https://example.org");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path is null);
@ -999,7 +998,7 @@ unittest
assert(u.fragment is null);
// With user and port and path
u = URL!()("https://hilary:putnam@example.org:443/foo/bar");
u = URL("https://hilary:putnam@example.org:443/foo/bar");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path == "/foo/bar");
@ -1009,7 +1008,7 @@ unittest
assert(u.fragment is null);
// With query string
u = URL!()("https://example.org/?login=true");
u = URL("https://example.org/?login=true");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path == "/");
@ -1017,14 +1016,14 @@ unittest
assert(u.fragment is null);
// With query string and fragment
u = URL!()("https://example.org/?login=false#label");
u = URL("https://example.org/?login=false#label");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path == "/");
assert(u.query == "login=false");
assert(u.fragment == "label");
u = URL!()("redis://root:password@localhost:2201/path?query=value#fragment");
u = URL("redis://root:password@localhost:2201/path?query=value#fragment");
assert(u.scheme == "redis");
assert(u.user == "root");
assert(u.pass == "password");
@ -1043,7 +1042,7 @@ private unittest
{
try
{
URL!()(t[0]);
URL(t[0]);
assert(0);
}
catch (URIException e)
@ -1053,7 +1052,7 @@ private unittest
}
else
{
auto u = URL!()(t[0]);
auto u = URL(t[0]);
assert("scheme" in t[1] ? u.scheme == t[1]["scheme"] : u.scheme is null,
t[0]);
assert("user" in t[1] ? u.user == t[1]["user"] : u.user is null, t[0]);
@ -1100,31 +1099,30 @@ enum Component : string
*
* Returns: Requested URL components.
*/
URL parseURL(U)(in U source)
if (isSomeString!U)
URL parseURL(typeof(null) T)(in char[] source)
{
return URL!U(source);
return URL(source);
}
/** ditto */
string parseURL(string T, U)(in U source)
if ((T == "scheme"
/// Ditto.
const(char)[] parseURL(immutable(char)[] T)(in char[] source)
if (T == "scheme"
|| T =="host"
|| T == "user"
|| T == "pass"
|| T == "path"
|| T == "query"
|| T == "fragment") && isSomeString!U)
|| T == "fragment")
{
auto ret = URL!U(source);
auto ret = URL(source);
return mixin("ret." ~ T);
}
/** ditto */
ushort parseURL(string T, U)(in U source)
if (T == "port" && isSomeString!U)
/// Ditto.
ushort parseURL(immutable(char)[] T)(in char[] source)
if (T == "port")
{
auto ret = URL!U(source);
auto ret = URL(source);
return ret.port;
}
@ -1158,7 +1156,7 @@ private unittest
else
{
ushort port = parseURL!(Component.port)(t[0]);
string component = parseURL!(Component.scheme)(t[0]);
auto component = parseURL!(Component.scheme)(t[0]);
assert("scheme" in t[1] ? component == t[1]["scheme"] : component is null,
t[0]);
component = parseURL!(Component.user)(t[0]);