/* * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Florent Guillaume */ package org.nuxeo.ecm.core.work; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import static org.nuxeo.ecm.core.work.api.Work.State.RUNNING; import static org.nuxeo.ecm.core.work.api.Work.State.SCHEDULED; import static org.nuxeo.ecm.core.work.api.Work.State.UNKNOWN; import java.io.File; import java.io.Serializable; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.nuxeo.common.logging.SequenceTracer; import org.nuxeo.ecm.core.work.api.WorkManager; import org.nuxeo.ecm.core.work.api.WorkManager.Scheduling; import org.nuxeo.ecm.core.work.api.WorkQueueDescriptor; import org.nuxeo.ecm.core.work.api.WorkQueueMetrics; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.test.NXRuntimeTestCase; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; import org.nuxeo.runtime.test.runner.FileEventsTrackingFeature; import org.nuxeo.runtime.trackers.files.FileEvent; @Features(FileEventsTrackingFeature.class) public class WorkManagerTest extends NXRuntimeTestCase { protected static class CreateFile extends AbstractWork implements Serializable { private final File file; private static final long serialVersionUID = 1L; protected CreateFile(File file) { this.file = file; } @Override public String getTitle() { return "pfouh"; } @Override public void work() { FileEvent.onFile(this, file, this).send(); SequenceTracer.mark("send event"); } } protected static class SleepAndFailWork extends SleepWork { private static final long serialVersionUID = 1L; public SleepAndFailWork(long durationMillis, boolean debug, String id) { super(durationMillis, debug, id); } @Override public void work() { super.work(); throw new RuntimeException(getTitle()); } } protected static final String CATEGORY = "SleepWork"; protected static final String QUEUE = "SleepWork"; protected WorkManagerImpl service; protected boolean dontClearCompletedWork; void assertSetEquals(List<String> expected, List<String> actual) { assertEquals(new HashSet<>(expected), new HashSet<>(actual)); } void assertMetrics(long scheduled, long running, long completed, long cancelled) { assertEquals(new WorkQueueMetrics(QUEUE, scheduled, running, completed, cancelled), service.getMetrics(QUEUE)); } @Override @Before public void setUp() throws Exception { super.setUp(); deployBundle("org.nuxeo.ecm.core.event"); service = (WorkManagerImpl) Framework.getLocalService(WorkManager.class); } protected void doDeploy() throws Exception { deployContrib("org.nuxeo.ecm.core.event.test", "test-workmanager-config.xml"); } protected void deployAndStart() throws Exception { doDeploy(); fireFrameworkStarted(); assertMetrics(0, 0, 0, 0); } // overridden for persistence public boolean persistent() { return false; // in-memory, no persistence } @Test public void testBasics() throws Exception { deployAndStart(); assertNotNull(service); assertMetrics(0, 0, 0, 0); } @Test public void testWorkManagerConfig() throws Exception { deployAndStart(); SleepWork work = new SleepWork(1); assertEquals(CATEGORY, work.getCategory()); assertEquals(QUEUE, service.getCategoryQueueId(CATEGORY)); WorkQueueDescriptor qd = service.getWorkQueueDescriptor(QUEUE); assertEquals("SleepWork", qd.id); assertEquals("Sleep Work Queue", qd.name); assertEquals(2, qd.getMaxThreads()); assertEquals(Collections.singleton("SleepWork"), qd.categories); } @Test public void testWorkManagerWork() throws Exception { deployAndStart(); int duration = 3000; // ms SleepWork work = new SleepWork(duration, false); service.schedule(work); assertTrue(work.getSchedulingTime() != 0); Thread.sleep(duration / 3); assertEquals(RUNNING, service.getWorkState(work.getId())); assertMetrics(0, 1, 0, 0); Thread.sleep(duration); assertMetrics(0, 0, 1, 0); // assertTrue(work.getStartTime() != 0); // assertTrue(work.getCompletionTime() != 0); // assertTrue(work.getCompletionTime() - work.getStartTime() > 0); } @Test public void testWorkManagerScheduling() throws Exception { deployAndStart(); int duration = 5000; // 2s SleepWork work1 = new SleepWork(duration, false, "1"); SleepWork work2 = new SleepWork(duration, false, "2"); SleepWork work3 = new SleepWork(duration, false, "3"); service.schedule(work1); service.schedule(work2); service.schedule(work3); Thread.sleep(duration / 2); assertMetrics(1, 2, 0, 0); assertEquals(RUNNING, service.getWorkState("1")); assertEquals(RUNNING, service.getWorkState("2")); assertEquals(SCHEDULED, service.getWorkState("3")); assertEquals(Collections.singletonList("3"), service.listWorkIds(QUEUE, SCHEDULED)); assertSetEquals(Arrays.asList("1", "2"), service.listWorkIds(QUEUE, RUNNING)); assertSetEquals(Arrays.asList("1", "2", "3"), service.listWorkIds(QUEUE, null)); // disabled IF_NOT_* features if (Boolean.FALSE.booleanValue()) { SleepWork work4 = new SleepWork(duration, false, "3"); // id=3 service.schedule(work4, Scheduling.IF_NOT_SCHEDULED); assertEquals(UNKNOWN, work4.getWorkInstanceState()); SleepWork work5 = new SleepWork(duration, false, "1"); // id=1 service.schedule(work5, Scheduling.IF_NOT_RUNNING_OR_SCHEDULED); assertEquals(UNKNOWN, work5.getWorkInstanceState()); } SleepWork work7 = new SleepWork(duration, false, "3"); // id=3 service.schedule(work7, Scheduling.CANCEL_SCHEDULED); assertEquals(SCHEDULED, work7.getWorkInstanceState()); assertMetrics(1, 2, 0, 1); SleepAndFailWork work8 = new SleepAndFailWork(0, false, "4"); service.schedule(work8); assertMetrics(2, 2, 0, 1); assertTrue(service.awaitCompletion(duration * 3, TimeUnit.MILLISECONDS)); assertMetrics(0, 0, 4, 1); assertEquals(Collections.emptyList(), service.listWorkIds(QUEUE, SCHEDULED)); assertEquals(Collections.emptyList(), service.listWorkIds(QUEUE, RUNNING)); assertEquals(Collections.emptyList(), service.listWorkIds(QUEUE, null)); } public void testDuplicatedWorks() throws Exception { deployAndStart(); int duration = 2000; // 2s service.enableProcessing("SleepWork", false); SleepWork work1 = new SleepWork(duration, false, "1"); SleepWork work2 = new SleepWork(duration, false, "1"); service.schedule(work1); service.schedule(work2); assertMetrics(1, 0, 0, 0); service.enableProcessing("SleepWork", true); assertTrue(service.awaitCompletion("SleepWork", duration * 10, TimeUnit.MILLISECONDS)); assertMetrics(0, 0, 1, 0); } @Test @Ignore public void testWorkManagerShutdown() throws Exception { deployAndStart(); int duration = 2000; // 2s SleepWork work1 = new SleepWork(duration, false, "1"); SleepWork work2 = new SleepWork(duration, false, "2"); SleepWork work3 = new SleepWork(duration, false, "3"); service.schedule(work1); service.schedule(work2); service.schedule(work3); Thread.sleep(duration / 2); assertEquals(RUNNING, service.getWorkState("1")); assertEquals(RUNNING, service.getWorkState("2")); assertEquals(SCHEDULED, service.getWorkState("3")); // shutdown workmanager service // work1 and work2 get a suspended notice and stop // work3 then gets scheduled immediately // and is either discarded (memory) // or put in the suspended queue (persistent) dontClearCompletedWork = true; boolean terminated = service.shutdown(duration * 2, TimeUnit.MILLISECONDS); assertTrue(terminated); // check work state assertEquals(SCHEDULED, work1.getWorkInstanceState()); assertEquals(SCHEDULED, work2.getWorkInstanceState()); assertEquals(persistent() ? SCHEDULED : UNKNOWN, work3.getWorkInstanceState()); long remaining1 = work1.durationMillis; long remaining2 = work2.durationMillis; long remaining3 = work3.durationMillis; assertTrue("remaining1 " + remaining1, remaining1 < duration); assertTrue("remaining2 " + remaining2, remaining2 < duration); assertEquals(duration, remaining3); } @Test public void testWorkManagerConfigDisableOneBeforeStart() throws Exception { doDeploy(); // before first applicationStarted: // disable SleepWork queue deployContrib("org.nuxeo.ecm.core.event.test", "test-workmanager-disablequeue.xml"); fireFrameworkStarted(); assertTrue(service.isProcessingEnabled("default")); assertFalse(service.isProcessingEnabled("SleepWork")); } @Test public void testWorkManagerConfigDisableOneAfterStart() throws Exception { deployAndStart(); // after first applicationStarted: // disable SleepWork queue service.enableProcessing("SleepWork", false); assertTrue(service.isProcessingEnabled("default")); assertFalse(service.isProcessingEnabled("SleepWork")); } @Test public void testWorkManagerConfigDisableAllBeforeStart() throws Exception { doDeploy(); deployContrib("org.nuxeo.ecm.core.event.test", "test-workmanager-disablequeue1.xml"); fireFrameworkStarted(); assertFalse(service.isProcessingEnabled("default")); assertTrue(service.isProcessingEnabled("SleepWork")); } @Test public void testWorkManagerConfigDisableAllAfterStart() throws Exception { deployAndStart(); service.enableProcessing(false); assertFalse(service.isProcessingEnabled()); service.enableProcessing("SleepWork", true); assertTrue(service.isProcessingEnabled()); } @Ignore("NXP-15680") @Test public void testWorkManagerDisableProcessing() throws Exception { deployAndStart(); assumeTrue(persistent()); // disable SleepWork queue deployContrib("org.nuxeo.ecm.core.event.test", "test-workmanager-disablequeue.xml"); int duration = 2000; // 2s SleepWork work1 = new SleepWork(duration, false); service.schedule(work1); Thread.sleep(duration / 2); // stays scheduled assertMetrics(1, 0, 0, 0); Thread.sleep(2 * duration); // still scheduled assertMetrics(1, 0, 0, 0); // now reactivate the queue // use a programmatic work queue descriptor WorkQueueDescriptor descr = new WorkQueueDescriptor(); descr.id = "SleepWork"; descr.processing = Boolean.TRUE; descr.categories = Collections.emptySet(); service.activateQueue(descr); Thread.sleep(duration / 2); assertMetrics(0, 1, 0, 0); Thread.sleep(duration); assertMetrics(0, 0, 1, 0); } @Ignore("NXP-15680") @Test public void testWorkManagerDisableProcessing2() throws Exception { deployAndStart(); assumeTrue(persistent()); // disable all queues deployContrib("org.nuxeo.ecm.core.event.test", "test-workmanager-disablequeue2.xml"); int duration = 2000; // 2s SleepWork work1 = new SleepWork(duration, false); service.schedule(work1); Thread.sleep(duration / 2); // stays scheduled assertMetrics(1, 0, 0, 0); // check that we can reenable the queue Thread.sleep(2 * duration); // still scheduled assertMetrics(1, 0, 0, 0); // now reactivate the queue // use a programmatic work queue descriptor WorkQueueDescriptor descr = new WorkQueueDescriptor(); descr.id = "SleepWork"; descr.processing = Boolean.TRUE; descr.categories = Collections.emptySet(); service.activateQueue(descr); Thread.sleep(duration / 2); assertMetrics(0, 1, 0, 0); Thread.sleep(duration); assertMetrics(0, 0, 1, 0); } @Inject public FeaturesRunner runner; protected FileEventsTrackingFeature feature; @Before public void injectFeature() { feature = runner.getFeature(FileEventsTrackingFeature.class); } @Test public void transientFilesWorkAreCleaned() throws Exception { deployAndStart(); final File file = feature.resolveAndCreate(new File("pfouh")); service.schedule(new CreateFile(file)); service.awaitCompletion(5, TimeUnit.SECONDS); } @Test public void testNoConcurrentJobsWithSameId() throws Exception { deployAndStart(); // create an init work to warm up the service, this is needed only for the embedded redis mode // sometime embedded mode takes around 1s to init, this prevent to put reliable assertion on time execution SleepWork initWork = new SleepWork(1, false, "0"); service.schedule(initWork); assertTrue(service.awaitCompletion(5, TimeUnit.SECONDS)); assertMetrics(0, 0, 1, 0); // Schedule a first work int durationMS = 3000; String workId = "1234"; SleepWork work = new SleepWork(durationMS, false, workId); service.schedule(work); // wait a bit to make sure it is running Thread.sleep(durationMS / 3); assertMetrics(0, 1, 1, 0); // schedule another work with the same workId // don't try to put a different duration, same work id means same work serializatoin SleepWork workbis = new SleepWork(durationMS, false, workId); service.schedule(workbis); // wait a bit, the first work is still running, the scheduled work should wait // because we don't want concurrent execution of work with the same workId Thread.sleep(durationMS / 3); assertMetrics(1, 1, 1, 0); // wait enough so the first work is done and the second should be running Thread.sleep(durationMS); assertMetrics(0, 1, 2, 0); assertTrue(service.awaitCompletion(2 * durationMS, TimeUnit.MILLISECONDS)); assertMetrics(0, 0, 3, 0); } }