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

org.apache.kafka.common.internals.IdempotentCloser Maven / Gradle / Ivy

There is a newer version: 3.9.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.kafka.common.internals;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

/**
 * {@code IdempotentCloser} encapsulates some basic logic to ensure that a given resource is only closed once.
 * The underlying mechanism for ensuring that the close only happens once and is thread safe
 * is via the {@link AtomicBoolean#compareAndSet(boolean, boolean)}. Users can provide callbacks (via optional
 * {@link Runnable}s) for either the initial close and/or any subsequent closes.
 *
 * 

* * Here's an example: * *

 *
 * public class MyDataFile implements Closeable {
 *
 *     private final IdempotentCloser closer = new IdempotentCloser();
 *
 *     private final File file;
 *
 *     . . .
 *
 *     public boolean write() {
 *         closer.assertOpen(() -> String.format("Data file %s already closed!", file));
 *         writeToFile();
 *     }
 *
 *     public boolean isClosed() {
 *         return closer.isClosed();
 *     }
 *
 *     @Override
 *     public void close() {
 *         Runnable onInitialClose = () -> {
 *             cleanUpFile(file);
 *             log.debug("Data file {} closed", file);
 *         };
 *         Runnable onSubsequentClose = () -> {
 *             log.warn("Data file {} already closed!", file);
 *         };
 *         closer.close(onInitialClose, onSubsequentClose);
 *     }
 * }
 * 
*/ public class IdempotentCloser implements AutoCloseable { private final AtomicBoolean isClosed; /** * Creates an {@code IdempotentCloser} that is not yet closed. */ public IdempotentCloser() { this(false); } /** * Creates an {@code IdempotentCloser} with the given initial state. * * @param isClosed Initial value for underlying state */ public IdempotentCloser(boolean isClosed) { this.isClosed = new AtomicBoolean(isClosed); } /** * This method serves as an assert that the {@link IdempotentCloser} is still open. If it is open, this method * simply returns. If it is closed, a new {@link IllegalStateException} will be thrown using the supplied message. * * @param message {@link Supplier} that supplies the message for the exception */ public void assertOpen(Supplier message) { if (isClosed.get()) throw new IllegalStateException(message.get()); } /** * This method serves as an assert that the {@link IdempotentCloser} is still open. If it is open, this method * simply returns. If it is closed, a new {@link IllegalStateException} will be thrown using the given message. * * @param message Message to use for the exception */ public void assertOpen(String message) { if (isClosed.get()) throw new IllegalStateException(message); } public boolean isClosed() { return isClosed.get(); } /** * Closes the resource in a thread-safe manner. * *

* * After the execution has completed, calls to {@link #isClosed()} will return {@code false} and calls to * {@link #assertOpen(String)} and {@link #assertOpen(Supplier)} * will throw an {@link IllegalStateException}. */ @Override public void close() { close(null, null); } /** * Closes the resource in a thread-safe manner. * *

* * After the execution has completed, calls to {@link #isClosed()} will return {@code false} and calls to * {@link #assertOpen(String)} and {@link #assertOpen(Supplier)} * will throw an {@link IllegalStateException}. * * @param onInitialClose Optional {@link Runnable} to execute when the resource is closed. Note that the * object will still be considered closed even if an exception is thrown during the course * of its execution; can be {@code null} */ public void close(final Runnable onInitialClose) { close(onInitialClose, null); } /** * Closes the resource in a thread-safe manner. * *

* * After the execution has completed, calls to {@link #isClosed()} will return {@code false} and calls to * {@link #assertOpen(String)} and {@link #assertOpen(Supplier)} * will throw an {@link IllegalStateException}. * * @param onInitialClose Optional {@link Runnable} to execute when the resource is closed. Note that the * object will still be considered closed even if an exception is thrown during the course * of its execution; can be {@code null} * @param onSubsequentClose Optional {@link Runnable} to execute if this resource was previously closed. Note that * no state will be affected if an exception is thrown during its execution; can be * {@code null} */ public void close(final Runnable onInitialClose, final Runnable onSubsequentClose) { if (isClosed.compareAndSet(false, true)) { if (onInitialClose != null) onInitialClose.run(); } else { if (onSubsequentClose != null) onSubsequentClose.run(); } } @Override public String toString() { return "IdempotentCloser{" + "isClosed=" + isClosed + '}'; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy