@Override public void close() { if (!this.closed.get()) { Futures.await(Services.stopAsync(this, this.executorService)); this.closed.set(true); } }
/** * Asynchronously stops a Service and returns a CompletableFuture that will indicate when it is stopped. * * @param service The Service to stop. * @param executor An Executor to use for callback invocations. * @return A CompletableFuture that will be completed when the service enters a TERMINATED state, or completed * exceptionally if the service enters a FAILED state. */ public static CompletableFuture<Void> stopAsync(Service service, Executor executor) { // Service.stopAsync() will not throw any exceptions, but will transition the Service to either TERMINATED // or FAILED. We need to register the listener before we attempt to stop. CompletableFuture<Void> result = new CompletableFuture<>(); onStop(service, () -> result.complete(null), result::completeExceptionally, executor); service.stopAsync(); return result; }
private CompletableFuture<Void> startSecondaryServicesAsync() { return CompletableFuture.allOf( Services.startAsync(this.metadataCleaner, this.executor), Services.startAsync(this.writer, this.executor)); }
/** * Creates a new Container and attempts to register it. This method works in an optimistic manner: it creates the * Container first and then attempts to register it, which should prevent us from having to lock on this entire method. * Creating new containers is cheap (we don't start them yet), so this operation should not take any extra resources. * * @param containerId The Id of the Container to start. * @return A CompletableFuture which will be completed with a ContainerHandle once the container has been started. */ private CompletableFuture<ContainerHandle> startContainerInternal(int containerId) { ContainerWithHandle newContainer = new ContainerWithHandle(this.factory.createStreamSegmentContainer(containerId), new SegmentContainerHandle(containerId)); ContainerWithHandle existingContainer = this.containers.putIfAbsent(containerId, newContainer); if (existingContainer != null) { // We had multiple concurrent calls to start this Container and some other request beat us to it. newContainer.container.close(); throw new IllegalArgumentException(String.format("Container %d is already registered.", containerId)); } log.info("Registered SegmentContainer {}.", containerId); // Attempt to Start the container, but first, attach a shutdown listener so we know to unregister it when it's stopped. Services.onStop( newContainer.container, () -> unregisterContainer(newContainer), ex -> handleContainerFailure(newContainer, ex), this.executor); return Services.startAsync(newContainer.container, this.executor) .thenApply(v -> newContainer.handle); }
@Override public SegmentContainer getContainer(int containerId) throws ContainerNotFoundException { Exceptions.checkNotClosed(this.closed.get(), this); ContainerWithHandle result = this.containers.getOrDefault(containerId, null); if (result == null || Services.isTerminating(result.container.state())) { throw new ContainerNotFoundException(containerId); } return result.container; }
@Override public CompletableFuture<ContainerHandle> startContainer(int containerId, Duration timeout) { Exceptions.checkNotClosed(this.closed.get(), this); // Check if container exists ContainerWithHandle existingContainer = this.containers.get(containerId); if (existingContainer != null) { if (!Services.isTerminating(existingContainer.container.state())) { // Container is already registered and not in the process of shutting down. throw new IllegalArgumentException(String.format("Container %d is already registered.", containerId)); } // Wait for the container to shut down, and then start a new one. return existingContainer.shutdownNotifier .thenComposeAsync(v -> startContainerInternal(containerId), this.executor); } else { // Start the container right away. return startContainerInternal(containerId); } }
@Override public void close() { if (!this.closed.get()) { Futures.await(Services.stopAsync(this, this.executor)); log.info("{}: Closed.", this.traceObjectId); this.closed.set(true); } }
/** * Creates a new instance of the SelfTest class. * * @param testConfig The configuration to use for the test. * @param builderConfig The configuration to use for building the StreamSegmentStore Service. */ SelfTest(TestConfig testConfig, ServiceBuilderConfig builderConfig) { Preconditions.checkNotNull(builderConfig, "builderConfig"); this.testConfig = Preconditions.checkNotNull(testConfig, "testConfig"); this.closed = new AtomicBoolean(); this.actors = new ArrayList<>(); this.testCompletion = new AtomicReference<>(); this.state = new TestState(); this.executor = ExecutorServiceHelpers.newScheduledThreadPool(testConfig.getThreadPoolSize(), "self-test"); this.store = StoreAdapter.create(testConfig, builderConfig, this.executor); this.dataSource = new ProducerDataSource(this.testConfig, this.state, this.store); Services.onStop(this, this::shutdownCallback, this::shutdownCallback, this.executor); this.reporter = new Reporter(this.state, this.testConfig, this.store::getStorePoolSnapshot, this.executor); }
private CompletableFuture<Void> tryStartOnce() { return CompletableFuture .supplyAsync(this::performRecovery, this.executor) .thenCompose(anyItemsRecovered -> Services.startAsync(this.operationProcessor, this.executor) .thenComposeAsync(v -> anyItemsRecovered ? CompletableFuture.completedFuture(null) : queueMetadataCheckpoint(), this.executor)); }
private CompletableFuture<Void> startWhenDurableLogOnline() { CompletableFuture<Void> isReady; CompletableFuture<Void> delayedStart; if (this.durableLog.isOffline()) { // Attach a listener to the DurableLog's awaitOnline() Future and initiate the services' startup when that // completes successfully. isReady = CompletableFuture.completedFuture(null); delayedStart = this.durableLog.awaitOnline() .thenComposeAsync(v -> initializeSecondaryServices(), this.executor); } else { // DurableLog is already online. Immediately initialize secondary services. In this particular case, it needs // to be done synchronously since we need to initialize Storage before notifying that we are fully started. isReady = initializeSecondaryServices(); delayedStart = isReady; } // Delayed start. Secondary services need not be started in order for us to accept requests. delayedStart.thenComposeAsync(v -> startSecondaryServicesAsync(), this.executor) .whenComplete((v, ex) -> { if (ex == null) { // Successful start. log.info("{}: Started.", this.traceObjectId); } else if (!(Exceptions.unwrap(ex) instanceof ObjectClosedException) || !Services.isTerminating(state())) { // If the delayed start fails, immediately shut down the Segment Container with the appropriate // exception. We should ignore ObjectClosedExceptions or other exceptions during a shutdown phase // since that's most likely due to us shutting down. doStop(ex); } }); return isReady; }
@Override public void close() { if (this.closed.compareAndSet(false, true)) { Futures.await(Services.stopAsync(this, this.executor)); this.storage.close(); log.info("Closed."); } }
/** * Creates a new instance of the DurableLog class. * * @param config Durable Log Configuration. * @param metadata The StreamSegment Container Metadata for the container which this Durable Log is part of. * @param dataFrameLogFactory A DurableDataLogFactory which can be used to create instances of DataFrameLogs. * @param readIndex A ReadIndex which can be used to store newly processed appends. * @param executor The Executor to use for async operations. * @throws NullPointerException If any of the arguments are null. */ public DurableLog(DurableLogConfig config, UpdateableContainerMetadata metadata, DurableDataLogFactory dataFrameLogFactory, ReadIndex readIndex, ScheduledExecutorService executor) { Preconditions.checkNotNull(config, "config"); this.metadata = Preconditions.checkNotNull(metadata, "metadata"); Preconditions.checkNotNull(dataFrameLogFactory, "dataFrameLogFactory"); Preconditions.checkNotNull(readIndex, "readIndex"); this.executor = Preconditions.checkNotNull(executor, "executor"); this.durableDataLog = dataFrameLogFactory.createDurableDataLog(metadata.getContainerId()); assert this.durableDataLog != null : "dataFrameLogFactory created null durableDataLog."; this.traceObjectId = String.format("DurableLog[%s]", metadata.getContainerId()); this.inMemoryOperationLog = createInMemoryLog(); this.memoryStateUpdater = new MemoryStateUpdater(this.inMemoryOperationLog, readIndex, this::triggerTailReads); MetadataCheckpointPolicy checkpointPolicy = new MetadataCheckpointPolicy(config, this::queueMetadataCheckpoint, this.executor); this.operationProcessor = new OperationProcessor(this.metadata, this.memoryStateUpdater, this.durableDataLog, checkpointPolicy, executor); Services.onStop(this.operationProcessor, this::queueStoppedHandler, this::queueFailedHandler, this.executor); this.tailReads = new HashSet<>(); this.closed = new AtomicBoolean(); this.delayedStart = new CompletableFuture<>(); this.delayedStartRetry = Retry.withExpBackoff(config.getStartRetryDelay().toMillis(), 1, Integer.MAX_VALUE) .retryWhen(ex -> Exceptions.unwrap(ex) instanceof DataLogDisabledException); }
@Override protected void doStart() { log.info("{}: Starting.", this.traceObjectId); Services.startAsync(this.durableLog, this.executor) .thenComposeAsync(v -> startWhenDurableLogOnline(), this.executor) .whenComplete((v, ex) -> { if (ex == null) { // We are started and ready to accept requests when DurableLog starts. All other (secondary) services // are not required for accepting new operations and can still start in the background. log.info("{}: DurableLog Started ({}).", this.traceObjectId, isOffline() ? "OFFLINE" : "Online"); notifyStarted(); } else { doStop(ex); } }); }
@Override public void close() { if (!this.closed.get()) { Futures.await(Services.stopAsync(this, this.executor)); this.operationProcessor.close(); this.durableDataLog.close(); // Call this again just in case we were not able to do it in doStop(). log.info("{}: Closed.", this.traceObjectId); this.closed.set(true); } }
private void shutdownWhenStopped(Service component, String componentName) { Consumer<Throwable> failedHandler = cause -> { log.warn("{}: {} failed. Shutting down StreamSegmentContainer.", this.traceObjectId, componentName, cause); if (state() == State.RUNNING) { // We can only stop the service if it's already running. During the stop it will pick up the failure cause // and terminate in failure. stopAsync(); } else if (state() == State.STARTING) { // We can only notify failed if we are starting. We cannot fail a service if it's already in a terminal state. notifyFailed(cause); } }; Runnable stoppedHandler = () -> { if (state() == State.STARTING || state() == State.RUNNING) { // The Component stopped but we are not in a stopping/terminal phase. We need to shut down right away. log.warn("{}: {} stopped unexpectedly (no error) but StreamSegmentContainer was not currently stopping. Shutting down StreamSegmentContainer.", this.traceObjectId, componentName); stopAsync(); } }; Services.onStop(component, stoppedHandler, failedHandler, this.executor); }
Services.startAsync(this.store, this.executor) .thenCompose(v -> this.dataSource.createStreams()) .thenRunAsync(() -> {
@Override public void close() { if (!this.closed.getAndSet(true)) { if (state() == State.RUNNING) { Futures.await(Services.stopAsync(this, this.executorService)); } synchronized (this.clients) { this.clients.clear(); } this.metrics.close(); log.info("{} Closed.", TRACE_OBJECT_ID); } }
@Override public CompletableFuture<Void> stopContainer(ContainerHandle handle, Duration timeout) { Exceptions.checkNotClosed(this.closed.get(), this); ContainerWithHandle result = this.containers.getOrDefault(handle.getContainerId(), null); if (result == null) { return CompletableFuture.completedFuture(null); // This could happen due to some race (or AutoClose) in the caller. } // Stop the container and then unregister it. return Services.stopAsync(result.container, this.executor); }
@Override public void close() { if (this.closed.compareAndSet(false, true)) { this.extensions.values().forEach(SegmentContainerExtension::close); Futures.await(Services.stopAsync(this, this.executor)); this.metadataCleaner.close(); this.writer.close(); this.durableLog.close(); this.readIndex.close(); this.storage.close(); log.info("{}: Closed.", this.traceObjectId); } }
@Override public void close() { if (!this.closed.get()) { try { Futures.await(Services.stopAsync(this, this.executor)); this.dataSource.deleteAllStreams() .exceptionally(ex -> { TestLogger.log(LOG_ID, "Unable to delete all Streams: %s.", ex); return null; }).join(); this.store.close(); } finally { ExecutorServiceHelpers.shutdown(this.executor); this.closed.set(true); TestLogger.log(LOG_ID, "Closed."); } } }