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

org.eclipse.jetty.util.SharedBlockingCallback Maven / Gradle / Ivy

There is a newer version: 12.0.13
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.util;

import java.io.Closeable;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Objects;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

/**
 * Provides a reusable {@link Callback} that can block the thread
 * while waiting to be completed.
 * 

* A typical usage pattern is: *

 * void someBlockingCall(Object... args) throws IOException
 * {
 *     try(Blocker blocker = sharedBlockingCallback.acquire())
 *     {
 *         someAsyncCall(args, blocker);
 *         blocker.block();
 *     }
 * }
 * 
*/ public class SharedBlockingCallback { private static final Logger LOG = Log.getLogger(SharedBlockingCallback.class); private static final Throwable IDLE = new ConstantThrowable("IDLE"); private static final Throwable SUCCEEDED = new ConstantThrowable("SUCCEEDED"); private static final Throwable FAILED = new ConstantThrowable("FAILED"); private final ReentrantLock _lock = new ReentrantLock(); private final Condition _idle = _lock.newCondition(); private final Condition _complete = _lock.newCondition(); private Blocker _blocker = new Blocker(); @Deprecated protected long getIdleTimeout() { return -1; } public Blocker acquire() throws IOException { long idle = getIdleTimeout(); _lock.lock(); try { while (_blocker._state != IDLE) { if (idle > 0 && (idle < Long.MAX_VALUE / 2)) { // Wait a little bit longer than the blocker might block if (!_idle.await(idle * 2, TimeUnit.MILLISECONDS)) throw new IOException(new TimeoutException()); } else _idle.await(); } _blocker._state = null; return _blocker; } catch (InterruptedException x) { throw new InterruptedIOException(); } finally { _lock.unlock(); } } public boolean fail(Throwable cause) { Objects.requireNonNull(cause); _lock.lock(); try { if (_blocker._state == null) { _blocker._state = new BlockerFailedException(cause); _complete.signalAll(); return true; } } finally { _lock.unlock(); } return false; } protected void notComplete(Blocker blocker) { LOG.warn("Blocker not complete {}", blocker); if (LOG.isDebugEnabled()) LOG.debug(new Throwable()); } /** * A Closeable Callback. * Uses the auto close mechanism to check block has been called OK. *

Implements {@link Callback} because calls to this * callback do not blocak, rather they wakeup the thread that is blocked * in {@link #block()} */ public class Blocker implements Callback, Closeable { private Throwable _state = IDLE; protected Blocker() { } @Override public InvocationType getInvocationType() { return InvocationType.NON_BLOCKING; } @Override public void succeeded() { _lock.lock(); try { if (_state == null) { _state = SUCCEEDED; _complete.signalAll(); } else { LOG.warn("Succeeded after {}", _state.toString()); if (LOG.isDebugEnabled()) LOG.debug(_state); } } finally { _lock.unlock(); } } @Override public void failed(Throwable cause) { _lock.lock(); try { if (_state == null) { if (cause == null) _state = FAILED; else if (cause instanceof BlockerTimeoutException) // Not this blockers timeout _state = new IOException(cause); else _state = cause; _complete.signalAll(); } else if (_state instanceof BlockerTimeoutException || _state instanceof BlockerFailedException) { // Failure arrived late, block() already // modified the state, nothing more to do. if (LOG.isDebugEnabled()) LOG.debug("Failed after {}", _state); } else { LOG.warn("Failed after {}: {}", _state, cause); if (LOG.isDebugEnabled()) { LOG.debug(_state); LOG.debug(cause); } } } finally { _lock.unlock(); } } /** * Block until the Callback has succeeded or failed and after the return leave in the state to allow reuse. This is useful for code that wants to * repeatable use a FutureCallback to convert an asynchronous API to a blocking API. * * @throws IOException if exception was caught during blocking, or callback was cancelled */ public void block() throws IOException { long idle = getIdleTimeout(); _lock.lock(); try { while (_state == null) { if (idle > 0) { // Waiting here may compete with the idle timeout mechanism, // so here we wait a little bit longer to favor the normal // idle timeout mechanism that will call failed(Throwable). long excess = Math.min(idle / 2, 1000); if (!_complete.await(idle + excess, TimeUnit.MILLISECONDS)) { // Method failed(Throwable) has not been called yet, // so we will synthesize a special TimeoutException. _state = new BlockerTimeoutException(); } } else { _complete.await(); } } if (_state == SUCCEEDED) return; if (_state == IDLE) throw new IllegalStateException("IDLE"); if (_state instanceof IOException) throw (IOException)_state; if (_state instanceof CancellationException) throw (CancellationException)_state; if (_state instanceof RuntimeException) throw (RuntimeException)_state; if (_state instanceof Error) throw (Error)_state; throw new IOException(_state); } catch (final InterruptedException e) { _state = e; throw new InterruptedIOException(); } finally { _lock.unlock(); } } /** * Check the Callback has succeeded or failed and after the return leave in the state to allow reuse. */ @Override public void close() { _lock.lock(); try { if (_state == IDLE) throw new IllegalStateException("IDLE"); if (_state == null) notComplete(this); } finally { try { // If we have a failure if (_state != null && _state != SUCCEEDED) // create a new Blocker _blocker = new Blocker(); else // else reuse Blocker _state = IDLE; _idle.signalAll(); _complete.signalAll(); } finally { _lock.unlock(); } } } @Override public String toString() { _lock.lock(); try { return String.format("%s@%x{%s}", Blocker.class.getSimpleName(), hashCode(), _state); } finally { _lock.unlock(); } } } private static class BlockerTimeoutException extends TimeoutException { } private static class BlockerFailedException extends Exception { public BlockerFailedException(Throwable cause) { super(cause); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy