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

org.neo4j.kernel.ha.SlaveUpdatePuller Maven / Gradle / Ivy

Go to download

This component provides the means to set up a cluster of Neo4j instances that act together as a cluster, providing Master-Slave replication and other features.

There is a newer version: 3.5.0-beta03
Show newest version
/*
 * Copyright (c) 2002-2016 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j 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 .
 */
package org.neo4j.kernel.ha;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

import org.neo4j.cluster.InstanceId;
import org.neo4j.com.ComException;
import org.neo4j.com.RequestContext;
import org.neo4j.com.Response;
import org.neo4j.com.TransactionObligationResponse;
import org.neo4j.com.TransactionStream;
import org.neo4j.com.storecopy.TransactionCommittingResponseUnpacker;
import org.neo4j.com.storecopy.TransactionObligationFulfiller;
import org.neo4j.concurrent.BinaryLatch;
import org.neo4j.kernel.AvailabilityGuard;
import org.neo4j.kernel.ha.com.RequestContextFactory;
import org.neo4j.kernel.ha.com.master.InvalidEpochException;
import org.neo4j.kernel.ha.com.master.Master;
import org.neo4j.kernel.ha.com.master.MasterImpl;
import org.neo4j.kernel.ha.com.slave.InvalidEpochExceptionHandler;
import org.neo4j.kernel.ha.com.slave.MasterClient;
import org.neo4j.kernel.impl.transaction.log.TransactionIdStore;
import org.neo4j.kernel.impl.util.CappedLogger;
import org.neo4j.kernel.impl.util.JobScheduler;
import org.neo4j.kernel.lifecycle.LifecycleAdapter;
import org.neo4j.logging.Log;
import org.neo4j.logging.LogProvider;

import static java.lang.System.currentTimeMillis;

/**
 * Able to pull updates from a master and apply onto this slave database.
 * 

* Updates are pulled and applied using a single and dedicated thread, created in here. No other threads are allowed to * pull and apply transactions on a slave. Calling one of the {@link Master#pullUpdates(RequestContext) pullUpdates} * {@link TransactionObligationFulfiller#fulfill(long)} will {@link SlaveUpdatePuller#poke() poke} that single thread, * so * that it gets going, if not already doing so, with its usual task of pulling updates and the caller which poked * the update thread will constantly poll to see if the transactions it is obliged to await have been applied. *

* Here comes a diagram of how the classes making up this functionality hangs together: *

*

 *
 *             -------- 1 -------------------->({@link MasterImpl master})
 *           /                                   |
 *          |                                   /
 *          | v--------------------- 2 --------
 *       ({@link MasterClient slave})
 *       | ^ \
 *       | |   -------- 3 -----
 *       | \                   \
 *       |  \                   v
 *       |   ---- 8 -----------({@link TransactionCommittingResponseUnpacker response unpacker})
 *       |                        |     ^
 *       9                        |     |
 *    (continue)                  4     7
 *                                |     |
 *                                v     |
 *                             ({@link UpdatePullingTransactionObligationFulfiller obligation fulfiller})
 *                                |     ^
 *                                |     |
 *                                5     6
 *                                |     |
 *                                v     |
 *                            ({@link SlaveUpdatePuller update puller})
 *
 * 
*

* In the above picture: *

*

    *
  1. Slave issues request to master, for example for locking an entity
  2. *
  3. Master responds with a {@link TransactionObligationResponse transaction obligation} telling the slave * which transaction it must have applied before continuing
  4. *
  5. The response from the master gets unpacked by the response unpacker...
  6. *
  7. ...which will be sent to the {@link UpdatePullingTransactionObligationFulfiller obligation fulfiller}...
  8. *
  9. ...which will ask the {@link SlaveUpdatePuller update puller}, a separate thread, to have that transaction * committed and applied. The calling thread will gently wait for that to happen.
  10. *
  11. The {@link SlaveUpdatePuller update puller} will pull updates until it reaches that desired transaction id, * and might actually continue passed that point if master has more transactions. The obligation fulfiller, * constantly polling for {@link TransactionIdStore#getLastClosedTransactionId() last applied transaction} * will notice when the obligation has been fulfilled.
  12. *
  13. Response unpacker finishes its call to {@link TransactionObligationFulfiller#fulfill(long) fulfill the * obligation}
  14. *
  15. Slave has fully received the response and is now able to...
  16. *
  17. ...continue
  18. *
*

* All communication, except actually pulling updates, work this way between slave and master. The only difference * in the pullUpdates case is that instead of receiving and fulfilling a transaction obligation, * {@link TransactionStream transaction data} is received and applied to store directly, in batches. * * @see org.neo4j.kernel.ha.UpdatePuller */ public class SlaveUpdatePuller extends LifecycleAdapter implements Runnable, UpdatePuller { public interface Monitor { void pulledUpdates( long lastAppliedTxId ); } public static final int LOG_CAP = Integer.getInteger( "org.neo4j.kernel.ha.SlaveUpdatePuller.LOG_CAP", 10 ); public static final long PARK_NANOS = TimeUnit.MILLISECONDS.toNanos( Integer.getInteger( "org.neo4j.kernel.ha.SlaveUpdatePuller.PARK_MILLIS", 100 ) ); public static final int AVAILABILITY_AWAIT_MILLIS = Integer.getInteger( "org.neo4j.kernel.ha.SlaveUpdatePuller.AVAILABILITY_AWAIT_MILLIS", 5000 ); public static final String UPDATE_PULLER_THREAD_PREFIX = "UpdatePuller@"; static final Condition NEXT_TICKET = new Condition() { @Override public boolean evaluate( int currentTicket, int targetTicket ) { return currentTicket >= targetTicket; } }; private volatile boolean halted; private final AtomicInteger targetTicket = new AtomicInteger(); private final AtomicInteger currentTicket = new AtomicInteger(); private final RequestContextFactory requestContextFactory; private final Master master; private final Log logger; private final CappedLogger invalidEpochCappedLogger; private final CappedLogger comExceptionCappedLogger; private final LastUpdateTime lastUpdateTime; private final InstanceId instanceId; private final AvailabilityGuard availabilityGuard; private InvalidEpochExceptionHandler invalidEpochHandler; private final Monitor monitor; private final JobScheduler jobScheduler; private volatile Thread updatePullingThread; private volatile BinaryLatch shutdownLatch; // Store under synchronised(this), load in update puller thread SlaveUpdatePuller( RequestContextFactory requestContextFactory, Master master, LastUpdateTime lastUpdateTime, LogProvider logging, InstanceId instanceId, AvailabilityGuard availabilityGuard, InvalidEpochExceptionHandler invalidEpochHandler, JobScheduler jobScheduler, Monitor monitor) { this.requestContextFactory = requestContextFactory; this.master = master; this.lastUpdateTime = lastUpdateTime; this.instanceId = instanceId; this.availabilityGuard = availabilityGuard; this.invalidEpochHandler = invalidEpochHandler; this.jobScheduler = jobScheduler; this.monitor = monitor; this.logger = logging.getLog( getClass() ); this.invalidEpochCappedLogger = new CappedLogger( logger ).setCountLimit( LOG_CAP ); this.comExceptionCappedLogger = new CappedLogger( logger ).setCountLimit( LOG_CAP ); } @Override public void run() { updatePullingThread = Thread.currentThread(); String oldName = updatePullingThread.getName(); updatePullingThread.setName( UPDATE_PULLER_THREAD_PREFIX + instanceId ); try { periodicallyPullUpdates(); } finally { updatePullingThread.setName( oldName ); updatePullingThread = null; shutdownLatch.release(); } } private void periodicallyPullUpdates() { while ( !halted ) { int round = targetTicket.get(); if ( currentTicket.get() < round ) { doPullUpdates(); currentTicket.set( round ); continue; } LockSupport.parkNanos( PARK_NANOS ); } } @Override public synchronized void init() throws Throwable { if ( shutdownLatch != null ) { return; // This SlaveUpdatePuller has already been initialised } shutdownLatch = new BinaryLatch(); jobScheduler.schedule( JobScheduler.Groups.pullUpdates, this ); } @Override public synchronized void shutdown() throws Throwable { if ( shutdownLatch == null ) { return; // This SlaveUpdatePuller has already been shut down } Thread thread = updatePullingThread; halted = true; LockSupport.unpark( thread ); shutdownLatch.await(); shutdownLatch = null; } @Override public void pullUpdates() throws InterruptedException { if ( !isActive() || !availabilityGuard.isAvailable( AVAILABILITY_AWAIT_MILLIS ) ) { return; } tryPullUpdates(); } @Override public boolean tryPullUpdates() throws InterruptedException { return await( NEXT_TICKET, false /*we're OK with the update puller becoming inactive while we await the condition*/ ); } @Override public void pullUpdates( Condition condition, boolean strictlyAssertActive ) throws InterruptedException { await( condition, strictlyAssertActive ); } /** * Gets the update puller going, if it's not already going, and waits for the supplied condition to be * fulfilled as part of the update pulling happening. * * @param condition {@link UpdatePuller.Condition} to wait for. * @param strictlyAssertActive if {@code true} then observing an inactive update puller, whether * {@link #shutdown() halted}, will throw an {@link IllegalStateException}, * otherwise if {@code false} just stop waiting and return {@code false}. * @return whether or not the condition was met. If {@code strictlyAssertActive} either * {@code true} will be returned or exception thrown, if puller became inactive. * If {@code !strictlyAssertActive} and puller became inactive then {@code false} is returned. * @throws InterruptedException if we were interrupted while awaiting the condition. * @throws IllegalStateException if {@code strictlyAssertActive} and the update puller * became inactive while awaiting the condition. */ private boolean await( Condition condition, boolean strictlyAssertActive ) throws InterruptedException { if ( !checkActive( strictlyAssertActive ) ) { return false; } int ticket = poke(); while ( !condition.evaluate( currentTicket.get(), ticket ) ) { if ( !checkActive( strictlyAssertActive ) ) { return false; } Thread.sleep( 1 ); } return true; } /** * @return {@code true} if active, {@code false} if inactive and {@code !strictlyAssertActive} * @throws IllegalStateException if inactive and {@code strictlyAssertActive}. */ private boolean checkActive( boolean strictlyAssertActive ) { if ( !isActive() ) { // The update puller is observed as being inactive // The caller strictly requires the update puller to be active so should throw exception if ( strictlyAssertActive ) { throw new IllegalStateException( this + " is not active" ); } // The caller is OK with ignoring an inactive update puller, so just return return false; } return true; } private int poke() { int result = this.targetTicket.incrementAndGet(); LockSupport.unpark( updatePullingThread ); return result; } public boolean isActive() { return !halted; } @Override public String toString() { return "UpdatePuller[halted:" + halted + ", current:" + currentTicket + ", target:" + targetTicket + "]"; } private void doPullUpdates() { try { RequestContext context = requestContextFactory.newRequestContext(); try ( Response ignored = master.pullUpdates( context ) ) { // Updates would be applied as part of response processing monitor.pulledUpdates( context.lastAppliedTransaction() ); } invalidEpochCappedLogger.reset(); comExceptionCappedLogger.reset(); } catch ( InvalidEpochException e ) { invalidEpochHandler.handle(); invalidEpochCappedLogger.warn( "Pull updates by " + this + " failed at the epoch check", e ); } catch ( ComException e ) { invalidEpochCappedLogger.warn( "Pull updates by " + this + " failed due to network error.", e ); } catch ( Throwable e ) { logger.error( "Pull updates by " + this + " failed", e ); } lastUpdateTime.setLastUpdateTime( currentTimeMillis() ); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy