/* * Copyright 2012 Jason Miller * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jj.script; import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.inject.Inject; import javax.inject.Singleton; import jj.execution.CurrentTask; import jj.execution.TaskRunner; import jj.util.SecureRandomHelper; /** * @author jason * */ @Singleton class ContinuationPendingCache { private final TaskRunner taskRunner; private final CurrentTask currentTask; @Inject ContinuationPendingCache(final TaskRunner taskRunner, final CurrentTask currentTask) { this.taskRunner = taskRunner; this.currentTask = currentTask; } private final ScriptTask<ScriptEnvironment<?>> reserved = new ScriptTask<ScriptEnvironment<?>>("", null) { @Override protected void begin() throws Exception { throw new AssertionError("reserved sentinel was run!"); } }; private final ScriptTask<ScriptEnvironment<?>> alreadyResumed = new ScriptTask<ScriptEnvironment<?>>("", null) { @Override protected void begin() throws Exception { throw new AssertionError("already resumed sentinel was run!"); } }; /** * tasks awaiting resumption. */ private final ConcurrentMap<String, ScriptTask<?>> resumableTasks = new ConcurrentHashMap<>(); /** * bulk removable of pending tasks * @param keys A collection of {@link PendingKey}s to remove */ void removePendingTasks(Collection<PendingKey> keys) { keys.forEach(key -> resumableTasks.remove(key.id())); } String uniqueID() { // wow. a do...while! String result; do { result = Integer.toHexString(SecureRandomHelper.nextInt()); } while (resumableTasks.putIfAbsent(result, reserved) != null); return result; } void storeForContinuation(final ScriptTask<?> task) { PendingKey pendingKey = task.pendingKey(); if (pendingKey != null) { if (!resumableTasks.replace(pendingKey.id(), reserved, task) && !resumableTasks.remove(pendingKey.id(), alreadyResumed) ) { throw new AssertionError("pending key being stored was not reserved or is already in use!"); } } } void resume(final PendingKey pendingKey, final Object result) { assert pendingKey != null : "attempting to resume without a pendingKey"; ScriptTask<?> task = resumableTasks.remove(pendingKey.id()); // probably not an assertion in the long run - people will at some point be sending bullshit results at this thing and // we will just ignore them. but that will be when one can do things like run with kernel assertions off :D // so NOT YET assert task != null : "asked to resume a nonexistent task"; if (task != reserved) { task.resumeWith(result); } else if (currentTask.currentIs(ScriptTask.class)) { // it resumed immediately, via some stroke of luck resumableTasks.putIfAbsent(pendingKey.id(), alreadyResumed); task = currentTask.currentAs(ScriptTask.class); // so reschedule the current task to run again task.resumeWith(result); } else { throw new AssertionError("asked to resume an unstored key from a non-ScriptTask. weird error, weird message!"); } taskRunner.execute(task); } }