/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.mongodb.repl.oplogreplier;
import com.eightkdata.mongowp.OpTime;
import com.google.inject.assistedinject.Assisted;
import com.torodb.core.concurrent.ConcurrentToolsFactory;
import com.torodb.core.services.IdleTorodbService;
import com.torodb.mongodb.repl.OplogManager;
import com.torodb.mongodb.repl.OplogManager.ReadOplogTransaction;
import com.torodb.mongodb.repl.ReplicationFilters;
import com.torodb.mongodb.repl.oplogreplier.OplogApplier.ApplyingJob;
import com.torodb.mongodb.repl.oplogreplier.fetcher.ContinuousOplogFetcher;
import com.torodb.mongodb.repl.oplogreplier.fetcher.ContinuousOplogFetcher.ContinuousOplogFetcherFactory;
import com.torodb.mongodb.repl.oplogreplier.fetcher.OplogFetcher;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import javax.inject.Inject;
/**
* A {@link OplogApplierService} that delegate on an {@link OplogApplier}.
*
* A new {@link ContinuousOplogFetcher} and a new {@link ApplyingJob} are created when this service
* start up and they are finished once the service stop.
*/
public class DefaultOplogApplierService extends IdleTorodbService implements OplogApplierService {
private static final Logger LOGGER = LogManager.getLogger(DefaultOplogApplierService.class);
private final OplogApplier oplogApplier;
private final ContinuousOplogFetcherFactory oplogFetcherFactory;
private final OplogManager oplogManager;
private final Callback callback;
private volatile boolean stopping;
private OplogFetcher fetcher;
private ApplyingJob applyJob;
private final ReplicationFilters replFilters;
private final ExecutorService selfExecutor;
private CompletableFuture<Void> onFinishFuture;
@Inject
public DefaultOplogApplierService(ThreadFactory threadFactory,
OplogApplier oplogApplier, OplogManager oplogManager,
ContinuousOplogFetcherFactory oplogFetcherFactory,
@Assisted Callback callback, ReplicationFilters replFilters,
ConcurrentToolsFactory concurrentToolsFactory) {
super(threadFactory);
this.oplogApplier = oplogApplier;
this.oplogFetcherFactory = oplogFetcherFactory;
this.oplogManager = oplogManager;
this.callback = callback;
this.replFilters = replFilters;
this.selfExecutor = concurrentToolsFactory.createExecutorService("oplog-applier-service", true,
1);
}
@Override
protected void startUp() throws Exception {
callback.waitUntilStartPermision();
fetcher = createFetcher();
applyJob = oplogApplier.apply(fetcher, new ApplierContext.Builder()
.setReapplying(false)
.setUpdatesAsUpserts(true)
.build()
);
onFinishFuture = applyJob.onFinish().thenAcceptAsync(tuple -> {
if (stopping) {
return;
}
switch (tuple.v1) {
case FINE:
case ROLLBACK:
callback.rollback(this, (RollbackReplicationException) tuple.v2);
break;
case UNEXPECTED:
case STOP:
callback.onError(this, tuple.v2);
break;
case CANCELLED:
callback.onError(
this,
new AssertionError("Unexpected cancellation of the applier while the "
+ "service is not stopping"));
break;
default:
callback.onError(
this,
new AssertionError("Unexpected "
+ OplogApplier.ApplyingJobFinishState.class.getSimpleName()
+ " found: " + tuple.v1 + " with error "
+ tuple.v2));
}
}, selfExecutor);
}
@Override
protected void shutDown() throws Exception {
LOGGER.debug("Shutdown requested");
stopping = true;
if (applyJob != null) {
if (!applyJob.onFinish().isDone()) {
LOGGER.trace("Applier has been already finished");
} else {
LOGGER.trace("Requesting to stop the stream");
applyJob.cancel();
applyJob.onFinish().join();
LOGGER.trace("Applier finished");
}
} else {
LOGGER.debug(serviceName() + " stopped before it was running?");
}
if (fetcher != null) {
LOGGER.trace("Closing the fetcher");
fetcher.close();
LOGGER.trace("Fetcher closed");
} else {
LOGGER.debug(serviceName() + " stopped before it was running?");
}
if (onFinishFuture != null) {
onFinishFuture.join();
}
List<Runnable> pendingTasks = selfExecutor.shutdownNow();
assert pendingTasks.isEmpty() : "Oplog applier service shutted down "
+ "before its task were correctly executed";
callback.onFinish(this);
}
private OplogFetcher createFetcher() {
OpTime lastAppliedOptime;
long lastAppliedHash;
try (ReadOplogTransaction oplogReadTrans = oplogManager.createReadTransaction()) {
lastAppliedOptime = oplogReadTrans.getLastAppliedOptime();
lastAppliedHash = oplogReadTrans.getLastAppliedHash();
}
return replFilters.filterOplogFetcher(
oplogFetcherFactory.createFetcher(lastAppliedHash, lastAppliedOptime)
);
}
}