blob: 024acff3ab1b0afdeaaddb4cbd69fc7e7f9ea627 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.v23.context;
import io.v.v23.verror.VException;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import java.util.concurrent.CountDownLatch;
/**
* The mechanism for carrying deadlines, cancellation, as well as other arbitrary values.
* <p>
* Application code receives contexts in two main ways:
* <p><ol>
* <li> A {@link VContext} is returned by {@link io.v.v23.V#init V.init}. This will generally be
* used to set up servers in {@code main}, or for stand-alone client programs:
* <p><blockquote><pre>
* public static void main(String[] args) {
* VContext ctx = V.init();
* doSomething(ctx);
* }
* </pre></blockquote><p></li>
* <li> A {@link VContext} is passed to every server method implementation as the first parameter:
* <p><blockquote><pre>
* public class MyServerImpl implements MyServer {
* {@literal @}Override
* public void get(VContext ctx, ServerCall call) throws VException;
* }
* </pre></blockquote><p></li></ol>
* Further contexts can be derived from the original context to refine the environment.
* For example if part of your operation requires a finer deadline you can create a new context to
* handle that part:
* <p><blockquote><pre>
* VContext ctx = V.init();
* // We'll use cacheCtx to lookup data in memcache; if it takes more than a
* // second to get data from memcache we should just skip the cache and
* // perform the slow operation.
* VContext cacheCtx = ctx.withTimeout(Duration.standardSeconds(1));
* try {
* fetchDataFromMemcache(cacheCtx, key);
* } catch (VException e) {
* if (e.is(DEADLINE_EXCEEDED)) {
* recomputeData(ctx, key);
* }
* }
* </pre></blockquote><p>
* {@link VContext}s form a tree where derived contexts are children of the contexts from which
* they were derived. Children inherit all the properties of their parent except for the property
* being replaced (the deadline in the example above).
* <p>
* {@link VContext}s are extensible. The {@link #value value}/{@link #withValue withValue} methods
* allow you to attach new information to the context and extend its capabilities. In the same way
* we derive new contexts via the {@code with} family of functions you can create methods to attach
* new data:
* <p><blockquote><pre>
* public class Auth {
* private class AuthKey {}
*
* private static final AuthKey KEY = new AuthKey();
*
* public VContext withAuth(VContext parent, Auth data) {
* return parent.withValue(KEY, data);
* }
*
* public Auth fromContext(VContext ctx) {
* return (Auth)ctx.Value(KEY);
* }
* }
* </pre></blockquote><p>
* Note that any type can be used as a key but the caller should preferrably use a private or
* protected type to prevent collisions (i.e., to prevent somebody else instantiating a key of the
* same type). Keys are tested for equality by comparing their value pairs
* {@code (getClass(), hashCode())}.
*/
public class VContext {
private static native VContext nativeCreate() throws VException;
/** Creates a new context with no data attached.
* <p>
* This function is meant for use in tests only - the preferred way of obtaining a fully
* initialized context is through the Vanadium runtime.
*
* @return a new root context with no data attached
*/
public static VContext create() {
try {
return nativeCreate();
} catch (VException e) {
throw new RuntimeException("Couldn't create new context", e);
}
}
private long nativePtr;
// Cached "done()" CountDownLatch, as we're supposed to return the same object on every call.
private volatile CountDownLatch doneLatch = null;
private native DateTime nativeDeadline(long nativePtr) throws VException;
private native CountDownLatch nativeDone(long nativePtr) throws VException;
private native Object nativeValue(long nativePtr, Object key) throws VException;
private native CancelableVContext nativeWithCancel(long nativePtr) throws VException;
private native CancelableVContext nativeWithDeadline(long nativePtr, DateTime deadline)
throws VException;
private native CancelableVContext nativeWithTimeout(long nativePtr, Duration timeout)
throws VException;
private native VContext nativeWithValue(long nativePtr, Object key, Object value)
throws VException;
private native void nativeFinalize(long nativePtr);
protected VContext(long nativePtr) {
this.nativePtr = nativePtr;
}
/**
* Returns the time at which this context will be automatically canceled, or {@code null}
* if this context doesn't have a deadline.
*/
public DateTime deadline() {
try {
return nativeDeadline(nativePtr);
} catch (VException e) {
throw new RuntimeException("Couldn't get deadline", e);
}
}
/**
* Returns a counter that will reach the count of zero when this context is canceled (either
* explicitly or via an expired deadline). Callers may block on this counter or periodically
* poll it. When the counter reaches value zero, any blocked threads are released.
* <p>
* Successive calls to this method must always return the same value. Implementations may
* return {@code null} if they can never be canceled.
*
* @return a counter that reaches the count of zero when this context is canceled or exceeds
* its deadline, or {@code null} if the counter can never be canceled.
*/
public CountDownLatch done() {
// NOTE(spetrovic): We may have to lock needlessly if nativeDone() returns a null
// CountDownLatch, but that's OK for now.
if (doneLatch != null) return doneLatch;
synchronized (this) {
if (doneLatch != null) return doneLatch;
try {
doneLatch = nativeDone(nativePtr);
return doneLatch;
} catch (VException e) {
throw new RuntimeException("Couldn't invoke done", e);
}
}
}
/**
* Returns data inside the context associated with the provided key. See {@link #withValue}
* for more information on associating data with contexts.
*
* @param key a key value is associated with.
* @return value associated with the key.
*/
public Object value(Object key) {
try {
return nativeValue(nativePtr, key);
} catch (VException e) {
throw new RuntimeException("Couldn't get value: ", e);
}
}
/**
* Returns a child of the current context that can be canceled. After
* {@link CancelableVContext#cancel cancel} is invoked on the new context, the counter returned
* by its {@link #done done} method (and all contexts further derived from it) will be set to
* zero.
* <p>
* It is expected that the new context will be canceled only by the caller that created it.
* This is the reason that the extended interface {@link CancelableVContext} exists:
* to discourage {@link CancelableVContext#cancel cancel} from being invoked by anybody other
* than the creator.
*
* @return a child of the current context that can be canceled.
*/
public CancelableVContext withCancel() {
try {
return nativeWithCancel(nativePtr);
} catch (VException e) {
throw new RuntimeException("Couldn't create cancelable context", e);
}
}
/**
* Returns a child of the current context that is automatically canceled after the provided
* deadline is reached. The returned context can also be canceled manually; this is in fact
* encouraged when the context is no longer needed in order to free up the timer resources.
* <p>
* It is expected that the new context will be (explicitly) canceled only by the caller that
* created it. This is the reason that the extended interface {@link CancelableVContext}
* exists: to discourage {@link CancelableVContext#cancel cancel} from being invoked by anybody
* other than the creator.
*
* @param deadline an absolute time after which the context will be canceled.
* @return a child of the current context that is automatically canceled after the
* provided deadline is reached.
*/
public CancelableVContext withDeadline(DateTime deadline) {
try {
return nativeWithDeadline(nativePtr, deadline);
} catch (VException e) {
throw new RuntimeException("Couldn't create context with deadline", e);
}
}
/**
* Returns a child of the current context that is automatically canceled after the provided
* duration of time. The returned context can also be canceled manually; this is in fact
* encouraged when the context is no longer needed in order to free up the timer resources.
* <p>
* It is expected that the new context will be (explicitly) canceled only by the caller that
* created it. This is the reason that the extended interface {@link CancelableVContext}
* exists: to discourage {@link CancelableVContext#cancel cancel} from being invoked by anybody
* other than the creator.
*
* @param timeout a duration of time after which the context will be canceled.
* @return a child of the current context that is automatically canceled after the
* provided duration of time.
*/
public CancelableVContext withTimeout(Duration timeout) {
try {
return nativeWithTimeout(nativePtr, timeout);
} catch (VException e) {
throw new RuntimeException("Couldn't create context with timeout", e);
}
}
/**
* Returns a child of the current context that additionally contains the given key and its
* associated value. A subsequent call to {@link #value value} will return the provided
* value. This method should be used only for data that is relevant across multiple API
* boundaries and not for passing extra parameters to functions and methods.
* <p>
* Any type can be used as a key but the caller should preferrably use a private or protected
* type to prevent collisions (i.e., to prevent somebody else instantiating a key of the
* same type). Keys are tested for equality by comparing their value pairs:
* {@code (getClass(), hashCode())}. The caller shouldn't count on implementation
* maintaining a reference to the provided key.
*
* @param key a key value is associated with.
* @param value a value associated with the key.
* @return a child of the current context that additionally contains the given key and its
* associated value
*/
public VContext withValue(Object key, Object value) {
try {
return nativeWithValue(nativePtr, key, value);
} catch (VException e) {
throw new RuntimeException("Couldn't create context with data:", e);
}
}
protected long nativePtr() {
return nativePtr;
}
@Override
protected void finalize() {
nativeFinalize(nativePtr);
}
}