All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.facebook.concurrency.linearization.Linearizer Maven / Gradle / Ivy

/*
 * Copyright (C) 2012 Facebook, Inc.
 *
 * 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 com.facebook.concurrency.linearization;

import org.apache.log4j.Logger;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * The idea here is that we want to impose a partial ordering on
 * a series of tasks. This class allows you to generate "Points" that
 * have a start() and complete() method.  LinearizationPoints cannot
 * start() until all previously created Points have called complete().  
 * A ConcurrentPoint may not start until the last LinearizationPoint has
 * called complete();
 * 
 * The following example guarantees that printing of point3 will happen
 * after point1 and point2, though the former two can come in any order
 * 
 * 
* {@code

    ExecutorService executor = Executors.newCachedThreadPool();
    Linearizer linearizer = new Linearizer();
    final ConcurrentPoint concurrentPoint1 = linearizer.createConurrentPoint();
    final ConcurrentPoint concurrentPoint2 = linearizer.createConurrentPoint();
    Runnable task1 = new Runnable() {
      @Override
      public void run() {
        concurrentPoint1.start();
       
        try {
          System.err.println("point1");
        } finally {
          concurrentPoint1.complete();
        }
      }
    };
    Runnable task2 = new Runnable() {
      @Override
      public void run() {
         concurrentPoint2.start();
       
        try {
          System.err.println("point2");
        } finally {
          concurrentPoint2.complete();
        }
      }
    };
    executor.execute(task1);
    executor.execute(task2);
    
    final LinearizationPoint linearizationPoint = 
      linearizer.createLinearizationPoint();
    Runnable task3 = new Runnable() {
      @Override
      public void run() {
        linearizationPoint.start();

        try {
          System.err.println("point3");
        } finally {
          linearizationPoint.complete();
        }
      }
    };
    
    executor.execute(task3);
    executor.shutdown();
  }
 
*/ public class Linearizer { private static final Logger LOG = Logger.getLogger(Linearizer.class); private static final long COMPLETE_WAIT_TIME_SECONDS = 300; private final AtomicReference pointCountRef = new AtomicReference(new AtomicInteger(0)); private final AtomicReference lastLinearizationPointRef = new AtomicReference(); /** * creates an lock-object such that other objects of this type * may interleave their start/complete calls. * * calling start() on the resulting ConcurrentPoint will block until * the previous LinearizationPoint calls complete() * * * @return */ public synchronized ConcurrentPoint createConcurrentPoint() { return new ConcurrentPointImpl( pointCountRef.get(), lastLinearizationPointRef.get() ); } /** * calling start() on the resulting LinearizationPoint will block * until all previously generated Points call complete() * * @return */ public synchronized LinearizationPoint createLinearizationPoint() { AtomicInteger nextPointCount = new AtomicInteger(); AtomicInteger previousPointCount = pointCountRef.getAndSet(nextPointCount); LinearizationPointImpl linearizationPoint = new LinearizationPointImpl(previousPointCount, nextPointCount); // set this so that subsequently generated ConcurrentPoints can // call linearizationPoint.waitForCompletion() lastLinearizationPointRef.set(linearizationPoint); return linearizationPoint; } private static class ConcurrentPointImpl implements ConcurrentPoint { private final AtomicInteger pointCount; private final LinearizationPoint previousLinearizationPoint; private final AtomicBoolean completed = new AtomicBoolean(false); private ConcurrentPointImpl( AtomicInteger pointCount, LinearizationPoint previousLinearizationPoint ) { pointCount.incrementAndGet(); this.previousLinearizationPoint = previousLinearizationPoint; this.pointCount = pointCount; } @Override public void start() { try { // if there is a previous LinearizationPoint, we cannot // start until it completesv if (previousLinearizationPoint != null) { previousLinearizationPoint.waitForCompletion(); } } catch (InterruptedException e) { throw new RuntimeException( "interrupted waiting for previous LinearizationPoint to complete" ); } } @Override public void complete() { if (completed.compareAndSet(false, true)) { int result = pointCount.decrementAndGet(); // if we are the last point in a virtual queue, signal any // LinearizationPoint that might be waiting on said virtual queue if (result == 0) { synchronized (pointCount) { pointCount.notifyAll(); } } } } } private static class LinearizationPointImpl implements LinearizationPoint { private final CountDownLatch startSignal = new CountDownLatch(1); private final CountDownLatch completeSignal = new CountDownLatch(1); private final AtomicBoolean completed = new AtomicBoolean(false); private final AtomicInteger previousPointCount; private final AtomicInteger nextPointCount; private LinearizationPointImpl( AtomicInteger previousPointCount, AtomicInteger nextPointCount ) { // we have to increment this so that if another LinearizationPoint // is generated after us, it won't start until we complete nextPointCount.incrementAndGet(); this.nextPointCount = nextPointCount; this.previousPointCount = previousPointCount; } private void waitUntilPreviousPointsComplete() { try { synchronized (previousPointCount) { while (previousPointCount.get() > 0) { previousPointCount.wait(5000); } } } catch (InterruptedException e) { throw new RuntimeException( "interrupted waiting for ConcurrentPoints", e) ; } } @Override public void start() { // we wait for any points in the previous virtual queue to complete // By transitivity, this means *all* previous points will be complete // by this point waitUntilPreviousPointsComplete(); startSignal.countDown(); } @Override public void complete() { if (completed.compareAndSet(false, true)) { int result = nextPointCount.decrementAndGet(); // if we are the last point in a virtual queue, signal any // LinearizationPoint that might be waiting on said virtual queue if (result == 0) { synchronized (nextPointCount) { nextPointCount.notifyAll(); } } completeSignal.countDown(); } } @Override public void waitForStart() throws InterruptedException { while (!startSignal.await(COMPLETE_WAIT_TIME_SECONDS, TimeUnit.SECONDS)) { LOG.info(String.format( "waited %d seconds for LinearizationPoint.start, will wait some more", COMPLETE_WAIT_TIME_SECONDS )); } } @Override public boolean waitForStart(long timeout, TimeUnit unit) throws InterruptedException { return startSignal.await(timeout, unit); } @Override public void waitForCompletion() throws InterruptedException { while (!completeSignal.await(COMPLETE_WAIT_TIME_SECONDS, TimeUnit.SECONDS)) { LOG.info(String.format( "waited %d seconds for LinearizationPoint.complete, will wait some more", COMPLETE_WAIT_TIME_SECONDS )); } } @Override public boolean waitForCompletion(long timeout, TimeUnit unit) throws InterruptedException { return completeSignal.await(timeout, unit); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy