Merge pull request #4 from deavmi/feature/timeoutable_gets

Timeout-able get()'s
This commit is contained in:
Tristan B. Velloza Kildaire 2023-09-25 14:51:14 +02:00 committed by GitHub
commit 4af4c00dc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 216 additions and 6 deletions

View File

@ -316,5 +316,89 @@ unittest
writeln("Future done");
writeln("Got response: ", response);
client.close();
}
version(unittest)
{
import core.time : dur;
import doap.client.exceptions : RequestTimeoutException;
import doap.client.request : CoapRequestFuture, RequestState;
}
/**
* Client testing
*
* See above except we test a timeout-based
* request future here.
*
* This test DOES time out
*/
unittest
{
CoapClient client = new CoapClient("coap.me", 5683);
CoapRequestFuture future = client.newRequestBuilder()
.payload(cast(ubyte[])"Hello this is Tristan!")
.token([69])
.post();
try
{
writeln("Future start");
CoapPacket response = future.get(dur!("msecs")(10));
// We should timeout and NOT get here
assert(false);
}
catch(RequestTimeoutException e)
{
// Ensure that we have the correct state
assert(future.getState() == RequestState.TIMEDOUT);
// We SHOULD time out
assert(true);
}
client.close();
}
/**
* Client testing
*
* See above except we test a timeout-based
* request future here.
*
* This test DOES NOT time out (it tests
* with a high-enough threshold)
*/
unittest
{
CoapClient client = new CoapClient("coap.me", 5683);
CoapRequestFuture future = client.newRequestBuilder()
.payload(cast(ubyte[])"Hello this is Tristan!")
.token([69])
.post();
try
{
writeln("Future start");
CoapPacket response = future.get(dur!("msecs")(400));
// Ensure that we have the correct state
assert(future.getState() == RequestState.COMPLETED);
// We SHOULD get here
assert(true);
}
catch(RequestTimeoutException e)
{
// We should NOT time out
assert(false);
}
client.close();
}

View File

@ -0,0 +1,71 @@
module doap.client.exceptions;
import doap.exceptions : CoapException;
import core.time : Duration;
public class CoapClientException : CoapException
{
this(string msg)
{
super(msg);
}
}
import doap.client.request : CoapRequestFuture;
import std.conv : to;
/**
* Thrown when a `CoapRequestFuture` has
* a `get(Duration)` call timeout
*/
public final class RequestTimeoutException : CoapClientException
{
/**
* The future we timed out on
*/
private CoapRequestFuture future;
/**
* Timeout time
*/
private Duration timeout;
/**
* Constructs a new timeout exception for
* the given future which timed out
*
* Params:
* future = the future we timed out on
* timeout = the time duration timed out
* on
*/
package this(CoapRequestFuture future, Duration timeout)
{
super("Timed out whilst waiting for "~to!(string)(future)~" after "~to!(string)(timeout));
this.future = future;
this.timeout = timeout;
}
/**
* Returns the future request which timed
* out and cause dthis exception to throw
* in the first place
*
* Returns: the `CoapRequestFuture`
*/
public CoapRequestFuture getFuture()
{
return this.future;
}
/**
* Returns the timeout period which
* was exceeded
*
* Returns: the `Duration`
*/
public Duration getTimeout()
{
return this.timeout;
}
}

View File

@ -1,4 +1,5 @@
module doap.client;
public import doap.client.client : CoapClient;
public import doap.client.request : CoapRequestBuilder, CoapRequestFuture, RequestState;
public import doap.client.request : CoapRequestBuilder, CoapRequestFuture, RequestState;
public import doap.client.exceptions : CoapClientException, RequestTimeoutException;

View File

@ -2,7 +2,7 @@ module doap.client.request;
import doap.client.client : CoapClient;
import doap.protocol;
import doap.exceptions;
import doap.client.exceptions;
import core.time : Duration;
import std.datetime.stopwatch : StopWatch, AutoStart;
@ -181,12 +181,15 @@ package class CoapRequestBuilder
* Params:
* tkn = the token
* Returns: this builder
* Throws:
* CoapClientException = invalid token
* length
*/
public CoapRequestBuilder token(ubyte[] tkn)
{
if(tkn.length > 8)
{
throw new CoapException("The token cannot be more than 8 bytes");
throw new CoapClientException("The token cannot be more than 8 bytes");
}
this.tkn = tkn;
@ -277,7 +280,12 @@ public enum RequestState
/**
* The future was cancelled
*/
CANCELLED
CANCELLED,
/**
* The future timed out
*/
TIMEDOUT
}
/**
@ -371,7 +379,7 @@ public class CoapRequestFuture
*
* Returns: the response as a `CoapPacket`
* Throws:
* CoapException on cancelled request
* CoapClientException on cancelled request
*/
public CoapPacket get()
{
@ -393,10 +401,56 @@ public class CoapRequestFuture
// On error
else
{
throw new CoapException("Request future cancelled");
throw new CoapClientException("Request future cancelled");
}
}
/**
* Blocks until the response is received
* but will unbllock if the timeout given
* is exceeded
*
* Returns: the response as a `CoapPacket`
* Throws:
* RequestTimeoutException on the
* future request timing out
* CoapClientException on cancellation
* of the request
*/
public CoapPacket get(Duration timeout)
{
// We can only wait on a condition if we
// ... first have a-hold of the lock
this.mutex.lock();
scope(exit)
{
// Unlock the lock (either from successfully
// ... waiting or timing out)
this.mutex.unlock();
}
// Await a response
if(this.condition.wait(timeout))
{
// If successfully completed
if(this.state == RequestState.COMPLETED)
{
return this.response;
}
// On error
else
{
throw new CoapClientException("Request future cancelled");
}
}
else
{
this.state = RequestState.TIMEDOUT;
throw new RequestTimeoutException(this, timeout);
}
}
/**
* Returns the state of this future
*