Skip to content

Commit babb3ef

Browse files
committed
Refactor RemoteAPI to not depends on the node implementation type
Spawning a new node in a foreign thread is a very different operation from creating a client. Having both function exposed as overload (constructors) was confusing and misleading for users. Additionally, one couldn't store arrays of `RemoteAPI!API` with different implementations.
1 parent ae7533e commit babb3ef

File tree

1 file changed

+148
-37
lines changed

1 file changed

+148
-37
lines changed

source/geod24/LocalRest.d

Lines changed: 148 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,62 @@ private struct ArgWrapper (T...)
7272
T args;
7373
}
7474

75-
/// Ditto
76-
public final class RemoteAPI (API, Implementation : API) : API
75+
/*******************************************************************************
76+
77+
A reference to an alread-instantiated node
78+
79+
This class serves the same purpose as a `RestInterfaceClient`:
80+
it is a client for an already instantiated rest `API` interface.
81+
82+
In order to instantiate a new server (in a remote thread), use the static
83+
`spawn` function.
84+
85+
Params:
86+
API = The interface defining the API to implement
87+
88+
*******************************************************************************/
89+
90+
public final class RemoteAPI (API) : API
7791
{
78-
static if (is(typeof(Implementation.__ctor)))
79-
private alias CtorParams = Parameters!(Implementation.__ctor);
80-
else
81-
private alias CtorParams = AliasSeq!();
92+
/***************************************************************************
93+
94+
Instantiate a node and start it
95+
96+
This is usually called from the main thread, which will start all the
97+
nodes and then start to process request.
98+
In order to have a connected network, no nodes in any thread should have
99+
a different reference to the same node.
100+
In practice, this means there should only be one `Tid` per "address".
101+
102+
Note:
103+
When the `RemoteAPI` returned by this function is finalized,
104+
the child thread will be shut down.
105+
This ownership mechanism should be replaced with reference counting
106+
in a later version.
107+
108+
Params:
109+
Impl = Type of the implementation to instantiate
110+
args = Arguments to the object's constructor
111+
112+
Returns:
113+
A `RemoteAPI` owning the node reference
114+
115+
***************************************************************************/
116+
117+
public static RemoteAPI!(API) spawn (Impl) (CtorParams!Impl args)
118+
{
119+
auto childTid = .spawn(&spawned!(Impl), args);
120+
return new RemoteAPI(childTid, true);
121+
}
122+
123+
/// Helper template to get the constructor's parameters
124+
private static template CtorParams (Impl)
125+
{
126+
static if (is(typeof(Impl.__ctor)))
127+
private alias CtorParams = Parameters!(Impl.__ctor);
128+
else
129+
private alias CtorParams = AliasSeq!();
130+
}
82131

83132
/***************************************************************************
84133
@@ -91,11 +140,12 @@ public final class RemoteAPI (API, Implementation : API) : API
91140
`std.concurrency.receive` is not `@safe`, so neither is this.
92141
93142
Params:
143+
Implementation = Type of the implementation to instantiate
94144
args = Arguments to `Implementation`'s constructor
95145
96146
***************************************************************************/
97147

98-
private static void spawned (CtorParams...) (CtorParams cargs)
148+
private static void spawned (Implementation) (CtorParams!Implementation cargs)
99149
{
100150
import std.format;
101151

@@ -149,45 +199,32 @@ public final class RemoteAPI (API, Implementation : API) : API
149199
/// Whether or not the destructor should destroy the thread
150200
private bool owner;
151201

152-
/***************************************************************************
202+
// Vibe.d mandates that method must be @safe
203+
@safe:
153204

154-
Instantiate a node node and start it
205+
/***************************************************************************
155206
156-
This is usually called from the main thread, which will start all the
157-
nodes and then start to process request.
158-
In order to have a connected network, no nodes in any thread should have
159-
a different reference to the same node.
160-
In practice, this means there should only be one `Tid` per `Hash`.
207+
Create an instante of a client
161208
162-
When this class is finalized, the child thread will be shut down.
209+
This connects to an already instantiated node.
210+
In order to instantiate a node, see the static `spawn` function.
163211
164212
Params:
165-
args = Arguments to the object's constructor
213+
tid = `std.concurrency.Tid` of the node.
214+
This can usually be obtained by `std.concurrency.locate`.
166215
167216
***************************************************************************/
168217

169-
public this (CtorParams...) (CtorParams args)
218+
public this (Tid tid) @nogc pure nothrow
170219
{
171-
this.childTid = spawn(&spawned!(CtorParams), args);
172-
this.owner = true;
220+
this(tid, false);
173221
}
174222

175-
// Vibe.d mandates that method must be @safe
176-
@safe:
177-
178-
/***************************************************************************
179-
180-
Create a reference to an already existing Tid
181-
182-
This overload should be used by non-main Threads to get a reference
183-
to an already instantiated Node.
184-
185-
***************************************************************************/
186-
187-
public this (Tid tid) @nogc pure nothrow
223+
/// Private overload used by `spawn`
224+
private this (Tid tid, bool isOwner) @nogc pure nothrow
188225
{
189226
this.childTid = tid;
190-
this.owner = false;
227+
this.owner = isOwner;
191228
}
192229

193230
public Tid tid () @nogc pure nothrow
@@ -250,7 +287,7 @@ unittest
250287
{ assert(0); }
251288
}
252289

253-
scope test = new RemoteAPI!(API, MockAPI)();
290+
scope test = RemoteAPI!API.spawn!MockAPI();
254291
assert(test.pubkey() == 42);
255292
}
256293

@@ -295,16 +332,16 @@ unittest
295332
const name = hash.to!string;
296333
auto tid = std.concurrency.locate(name);
297334
if (tid != tid.init)
298-
return new RemoteAPI!(API, Node)(tid);
335+
return new RemoteAPI!API(tid);
299336

300337
switch (type)
301338
{
302339
case "normal":
303-
auto ret = new RemoteAPI!(API, Node)(false);
340+
auto ret = RemoteAPI!API.spawn!Node(false);
304341
std.concurrency.register(name, ret.tid());
305342
return ret;
306343
case "byzantine":
307-
auto ret = new RemoteAPI!(API, Node)(true);
344+
auto ret = RemoteAPI!API.spawn!Node(true);
308345
std.concurrency.register(name, ret.tid());
309346
return ret;
310347
default:
@@ -336,3 +373,77 @@ unittest
336373
// Make sure our main thread terminates after everyone else
337374
receiveOnly!int();
338375
}
376+
377+
/// This network have different types of nodes in it
378+
unittest
379+
{
380+
static interface API
381+
{
382+
@safe:
383+
public @property ulong requests ();
384+
public @property ulong value ();
385+
}
386+
387+
static class MasterNode : API
388+
{
389+
@safe:
390+
public override @property ulong requests()
391+
{
392+
return this.requests_;
393+
}
394+
395+
public override @property ulong value()
396+
{
397+
this.requests_++;
398+
return 42; // Of course
399+
}
400+
401+
private ulong requests_;
402+
}
403+
404+
static class SlaveNode : API
405+
{
406+
@safe:
407+
this(Tid masterTid)
408+
{
409+
this.master = new RemoteAPI!API(masterTid);
410+
}
411+
412+
public override @property ulong requests()
413+
{
414+
return this.requests_;
415+
}
416+
417+
public override @property ulong value()
418+
{
419+
this.requests_++;
420+
return master.value();
421+
}
422+
423+
private API master;
424+
private ulong requests_;
425+
}
426+
427+
API[4] nodes;
428+
auto master = RemoteAPI!API.spawn!MasterNode();
429+
nodes[0] = master;
430+
nodes[1] = RemoteAPI!API.spawn!SlaveNode(master.tid());
431+
nodes[2] = RemoteAPI!API.spawn!SlaveNode(master.tid());
432+
nodes[3] = RemoteAPI!API.spawn!SlaveNode(master.tid());
433+
434+
foreach (n; nodes)
435+
{
436+
assert(n.requests() == 0);
437+
assert(n.value() == 42);
438+
}
439+
440+
assert(nodes[0].requests() == 4);
441+
442+
foreach (n; nodes[1 .. $])
443+
{
444+
assert(n.value() == 42);
445+
assert(n.requests() == 2);
446+
}
447+
448+
assert(nodes[0].requests() == 7);
449+
}

0 commit comments

Comments
 (0)