| /******************************************************************************* |
| * Copyright (c) 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.core.tests.runtime.jobs; |
| |
| import java.util.*; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.atomic.AtomicLong; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.junit.FixMethodOrder; |
| import org.junit.Test; |
| import org.junit.runners.MethodSorters; |
| |
| /** |
| * Test for bug 574883 |
| */ |
| @FixMethodOrder(MethodSorters.NAME_ASCENDING) |
| public class Bug_574883 extends AbstractJobManagerTest { |
| |
| static class SerialExecutor extends Job { |
| |
| private final List<Runnable> queue; |
| private final Object myFamily; |
| |
| /** |
| * @param jobName descriptive job name |
| * @param family non null object to control this job execution |
| **/ |
| public SerialExecutor(String jobName, Object family) { |
| super(jobName); |
| Assert.isNotNull(family); |
| this.myFamily = family; |
| this.queue = Collections.synchronizedList(new LinkedList<>()); |
| setSystem(true); |
| } |
| |
| @Override |
| public boolean belongsTo(Object family) { |
| return myFamily == family; |
| } |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| Runnable action = queue.remove(0); |
| try { |
| if (action != null && !monitor.isCanceled()) { |
| action.run(); |
| } |
| } finally { |
| if (!queue.isEmpty() && !monitor.isCanceled()) { |
| // this call confuses JobManager and causes bug 574883 if the action above |
| // runs *too fast* |
| schedule(); |
| } |
| } |
| return Status.OK_STATUS; |
| } |
| |
| /** |
| * Enqueue an action asynchronously. |
| */ |
| public void schedule(Runnable action) { |
| queue.add(action); |
| schedule(); |
| } |
| } |
| |
| final int RUNS = 100_000; |
| final int processors = Runtime.getRuntime().availableProcessors(); |
| |
| @Test |
| public void testReschedulingLambda() throws InterruptedException { |
| // Executor has to execute every task. Even when they are scheduled fast |
| // and execute fast |
| SerialExecutor serialExecutor = new SerialExecutor("test", this); |
| AtomicInteger executions = new AtomicInteger(); |
| for (int i = 0; i < RUNS; i++) { |
| serialExecutor.schedule(() -> executions.incrementAndGet()); |
| } |
| Job.getJobManager().join(this, null); |
| Job[] jobs = Job.getJobManager().find(this); |
| int length = jobs.length; |
| int firstState = executions.get(); |
| try { |
| if (length > 0) { |
| // Check if that still would work? |
| Job.getJobManager().join(this, null); |
| if (Job.getJobManager().find(this).length > 0) { |
| fail("Job still running after second join, executed before: " + firstState + ", executed now: " |
| + executions.get() + ", cpu: " + processors); |
| } |
| } |
| assertEquals("Job still running after first join, executed: " + firstState + ", cpu: " + processors, 0, |
| length); |
| assertEquals(RUNS, executions.get()); |
| } catch (Throwable t) { |
| // TODO: print the fail only, as long as bug 574883 is not fixed yet |
| t.printStackTrace(System.out); |
| t.printStackTrace(System.err); |
| Job.getJobManager().cancel(this); |
| Thread.sleep(1000); |
| Job.getJobManager().join(this, null); |
| } |
| } |
| |
| @Test |
| public void testReschedulingMethodRef() throws InterruptedException { |
| // Executor has to execute every task. Even when they are scheduled fast |
| // and execute fast |
| SerialExecutor serialExecutor = new SerialExecutor("test", this); |
| AtomicInteger executions = new AtomicInteger(); |
| for (int i = 0; i < RUNS; i++) { |
| serialExecutor.schedule(executions::incrementAndGet); |
| } |
| Job.getJobManager().join(this, null); |
| Job[] jobs = Job.getJobManager().find(this); |
| int length = jobs.length; |
| int firstState = executions.get(); |
| try { |
| if (length > 0) { |
| // Check if that still would work? |
| Job.getJobManager().join(this, null); |
| if (Job.getJobManager().find(this).length > 0) { |
| fail("Job still running after second join, executed before: " + firstState + ", executed now: " |
| + executions.get() + ", cpu: " + processors); |
| } |
| } |
| assertEquals("Job still running after first join, executed: " + firstState + ", cpu: " + processors, 0, |
| length); |
| assertEquals(RUNS, executions.get()); |
| } catch (Throwable t) { |
| // TODO: print the fail only, as long as bug 574883 is not fixed yet |
| t.printStackTrace(System.out); |
| t.printStackTrace(System.err); |
| Job.getJobManager().cancel(this); |
| Thread.sleep(1000); |
| Job.getJobManager().join(this, null); |
| } |
| } |
| |
| /** |
| * This test always passes because it does a bit more work as both tests above |
| * inside run method |
| */ |
| @Test |
| public void testReschedulingSomeMoreWork() throws InterruptedException { |
| // Executor has to execute every task. Even when they are scheduled fast |
| // and execute fast |
| SerialExecutor serialExecutor = new SerialExecutor("test", this); |
| AtomicInteger executions = new AtomicInteger(); |
| AtomicLong garbage = new AtomicLong(42); |
| for (int i = 0; i < RUNS; i++) { |
| serialExecutor.schedule(() -> { |
| executions.incrementAndGet(); |
| // just consume some more CPU cycles |
| garbage.getAndUpdate(x -> (long) (x + Math.sin(x) * 100)); |
| }); |
| } |
| Job.getJobManager().join(this, null); |
| Job[] jobs = Job.getJobManager().find(this); |
| int length = jobs.length; |
| int firstState = executions.get(); |
| System.out.println(garbage); |
| try { |
| if (length > 0) { |
| // Check if that still would work? |
| Job.getJobManager().join(this, null); |
| if (Job.getJobManager().find(this).length > 0) { |
| fail("Job still running after second join, executed before: " + firstState + ", executed now: " |
| + executions.get() + ", cpu: " + processors); |
| } |
| } |
| assertEquals("Job still running after first join, executed: " + firstState + ", cpu: " + processors, 0, |
| length); |
| assertEquals(RUNS, executions.get()); |
| } catch (Throwable t) { |
| // TODO: print the fail only, as long as bug 574883 is not fixed yet |
| t.printStackTrace(System.out); |
| t.printStackTrace(System.err); |
| Job.getJobManager().cancel(this); |
| Thread.sleep(1000); |
| Job.getJobManager().join(this, null); |
| } |
| } |
| |
| @Test |
| public void testNow() throws Exception { |
| AtomicInteger executions = new AtomicInteger(); |
| for (int i = 0; i < RUNS; i++) { |
| long t1 = now(); |
| ((Runnable) () -> executions.incrementAndGet()).run(); |
| long t2 = now(); |
| long diff = t2 - t1; |
| assertTrue("Time should not go back: " + diff + " at: " + i, diff >= 0); |
| } |
| assertEquals(RUNS, executions.get()); |
| } |
| } |