Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
import java.util.function.Function;
import org.byteveda.taskito.core.CoreFacade;
import org.byteveda.taskito.errors.SerializationException;
import org.byteveda.taskito.errors.WorkflowException;
Expand All @@ -29,6 +31,11 @@
import org.byteveda.taskito.model.TaskLog;
import org.byteveda.taskito.model.TaskMetric;
import org.byteveda.taskito.model.WorkerInfo;
import org.byteveda.taskito.resources.ResourceContext;
import org.byteveda.taskito.resources.ResourceDefinition;
import org.byteveda.taskito.resources.ResourceRuntime;
import org.byteveda.taskito.resources.ResourceScope;
import org.byteveda.taskito.resources.ResourceStat;
import org.byteveda.taskito.scheduling.PeriodicTask;
import org.byteveda.taskito.serialization.Serializer;
import org.byteveda.taskito.spi.QueueBackend;
Expand All @@ -55,6 +62,7 @@ final class DefaultTaskito implements Taskito {
private final CoreFacade facade;
private final Serializer serializer;
private final List<Middleware> middleware = new CopyOnWriteArrayList<>();
private final ResourceRuntime resources = new ResourceRuntime();
Comment thread
pratyush618 marked this conversation as resolved.

DefaultTaskito(QueueBackend backend, Serializer serializer) {
this.backend = backend;
Expand All @@ -73,6 +81,36 @@ public Taskito use(Middleware middleware) {
return this;
}

// ── Resources ────────────────────────────────────────────────────

@Override
public <T> Taskito resource(String name, Function<ResourceContext, T> factory) {
return resource(name, ResourceScope.WORKER, factory);
}

@Override
public <T> Taskito resource(String name, ResourceScope scope, Function<ResourceContext, T> factory) {
resources.register(name, new ResourceDefinition(factory::apply, scope, null));
return this;
}

@Override
public <T> Taskito resource(
String name, ResourceScope scope, Function<ResourceContext, T> factory, Consumer<T> dispose) {
resources.register(name, new ResourceDefinition(factory::apply, scope, value -> dispose.accept(cast(value))));
return this;
}

@Override
public Map<String, ResourceStat> resourceMetrics() {
return resources.metrics();
}

@SuppressWarnings("unchecked")
private static <T> T cast(Object value) {
return (T) value;
}

// ── Producer ────────────────────────────────────────────────────

@Override
Expand Down Expand Up @@ -576,7 +614,8 @@ private static String encodeGate(GateConfig gate) {

@Override
public Worker.Builder worker() {
return Worker.builder(backend, serializer, middleware);
// Each worker gets its own runtime (own WORKER-scoped cache) over shared definitions.
return Worker.builder(backend, serializer, middleware, resources.forWorker());
}

@Override
Expand Down
19 changes: 19 additions & 0 deletions sdks/java/src/main/java/org/byteveda/taskito/Taskito.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import org.byteveda.taskito.errors.ConfigurationException;
import org.byteveda.taskito.internal.JniQueueBackend;
import org.byteveda.taskito.locks.Lock;
Expand All @@ -25,6 +27,9 @@
import org.byteveda.taskito.model.TaskLog;
import org.byteveda.taskito.model.TaskMetric;
import org.byteveda.taskito.model.WorkerInfo;
import org.byteveda.taskito.resources.ResourceContext;
import org.byteveda.taskito.resources.ResourceScope;
import org.byteveda.taskito.resources.ResourceStat;
import org.byteveda.taskito.scheduling.PeriodicTask;
import org.byteveda.taskito.serialization.JsonSerializer;
import org.byteveda.taskito.serialization.Serializer;
Expand Down Expand Up @@ -55,6 +60,20 @@ static Builder builder() {
/** Register cross-cutting middleware (enqueue + worker hooks); returns {@code this}. */
Taskito use(Middleware middleware);

// ── Resources (worker-side dependency injection) ─────────────────

/** Register a worker-scoped resource resolved in handlers via {@code Resources.use(name)}. */
<T> Taskito resource(String name, Function<ResourceContext, T> factory);

/** Register a resource with an explicit {@link ResourceScope}. */
<T> Taskito resource(String name, ResourceScope scope, Function<ResourceContext, T> factory);

/** Register a resource with a scope and a disposer run when the scope ends. */
<T> Taskito resource(String name, ResourceScope scope, Function<ResourceContext, T> factory, Consumer<T> dispose);

/** Per-resource counters (created / disposed / active). */
Map<String, ResourceStat> resourceMetrics();

// ── Producer ────────────────────────────────────────────────────

/** Enqueue a typed payload using the task's default options; returns the job id. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.byteveda.taskito.errors;

import org.byteveda.taskito.TaskitoException;

/**
* A worker resource could not be built, resolved, or disposed — e.g. a factory
* threw, an unknown resource name was requested, or {@code Resources.use} was
* called outside a task handler.
*/
public class ResourceException extends TaskitoException {
public ResourceException(String message) {
super(message);
}

public ResourceException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.byteveda.taskito.internal;

/**
* A single seam for per-task context propagation. Backed by a {@link ThreadLocal}
* today (the SDK targets Java 17); if the floor later rises to a JDK with a
* stable {@code ScopedValue}, only this class changes — callers are unaffected.
*
* <p>Each task runs on a pooled worker thread, so callers must {@link #set} on
* entry and {@link #clear} in a {@code finally} on exit to avoid leaking context
* into the next task scheduled on that thread.
*
* @param <T> the value carried for the duration of a task
*/
public final class ScopeContext<T> {
private final ThreadLocal<T> holder = new ThreadLocal<>();

/** The value bound on the current thread, or {@code null} if none. */
public T get() {
return holder.get();
}

/** Bind {@code value} on the current thread. */
public void set(T value) {
holder.set(value);
}

/** Unbind any value on the current thread. */
public void clear() {
holder.remove();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.byteveda.taskito.resources;

/**
* Handed to a resource factory so it can depend on other resources. A
* {@code WORKER} factory may only {@link #use} other worker resources; a
* {@code TASK} factory may use either.
*/
public interface ResourceContext {
/** The scope the factory is building for. */
ResourceScope scope();

/** Resolve another resource by name (building it if needed). */
<T> T use(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.byteveda.taskito.resources;

import java.util.function.Consumer;
import java.util.function.Function;

/**
* How to build (and optionally dispose) a resource.
*
* @param factory builds the resource, possibly using others via the context
* @param scope the resource's lifetime (defaults to {@link ResourceScope#WORKER})
* @param dispose optional cleanup run when the scope ends ({@code null} for none)
*/
public record ResourceDefinition(
Function<ResourceContext, Object> factory, ResourceScope scope, Consumer<Object> dispose) {

public ResourceDefinition {
if (factory == null) {
throw new IllegalArgumentException("resource factory must not be null");
}
if (scope == null) {
scope = ResourceScope.WORKER;
}
}

/** A worker-scoped resource with no disposer. */
public static ResourceDefinition worker(Function<ResourceContext, Object> factory) {
return new ResourceDefinition(factory, ResourceScope.WORKER, null);
}

/** A task-scoped resource with no disposer. */
public static ResourceDefinition task(Function<ResourceContext, Object> factory) {
return new ResourceDefinition(factory, ResourceScope.TASK, null);
}
}
Loading