package com.zendesk.maxwell.schema; import com.zendesk.maxwell.MaxwellContext; import com.zendesk.maxwell.replication.Position; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.SQLException; import com.zendesk.maxwell.replication.BinlogPosition; import com.zendesk.maxwell.util.RunLoopProcess; public class PositionStoreThread extends RunLoopProcess implements Runnable { static final Logger LOGGER = LoggerFactory.getLogger(PositionStoreThread.class); private Position position; // in memory position private Position storedPosition; // position as flushed to storage private final MysqlPositionStore store; private MaxwellContext context; private Exception exception; private Thread thread; private BinlogPosition lastHeartbeatSentFrom; // last position we sent a heartbeat from private long lastHeartbeatSent; public PositionStoreThread(MysqlPositionStore store, MaxwellContext context) { this.store = store; this.context = context; lastHeartbeatSentFrom = null; lastHeartbeatSent = 0L; } public void start() { this.thread = new Thread(this, "Position Flush Thread"); this.thread.setDaemon(true); thread.start(); } @Override public void run() { try { runLoop(); } catch ( Exception e ) { this.exception = e; context.terminate(e); } finally { this.taskState.stopped(); } } @Override public void requestStop() { super.requestStop(); thread.interrupt(); } @Override protected void beforeStop() { if ( exception == null ) { try { storeFinalPosition(); } catch ( Exception e ) { LOGGER.error("error storing final position: " + e); } } } void storeFinalPosition() throws SQLException { if ( position != null && !position.equals(storedPosition) ) { LOGGER.info("Storing final position: " + position); store.set(position); } } public void heartbeat() throws Exception { store.heartbeat(); } boolean shouldHeartbeat(Position currentPosition) { if ( currentPosition == null ) return true; if ( lastHeartbeatSentFrom == null ) return true; BinlogPosition currentBinlog = currentPosition.getBinlogPosition(); if ( !lastHeartbeatSentFrom.getFile().equals(currentBinlog.getFile()) ) return true; if ( currentBinlog.getOffset() - lastHeartbeatSentFrom.getOffset() > 1000 ) { return true; } long secondsSinceHeartbeat = (System.currentTimeMillis() - lastHeartbeatSent) / 1000; if ( secondsSinceHeartbeat >= 10 ) { // during quiet times, heartbeat at least every 10s return true; } return false; } public void work() throws Exception { Position newPosition = position; if ( newPosition != null && newPosition.newerThan(storedPosition) ) { store.set(newPosition); storedPosition = newPosition; } try { Thread.sleep(1000); } catch (InterruptedException e) { } if ( shouldHeartbeat(newPosition) ) { lastHeartbeatSent = store.heartbeat(); if (newPosition != null) { lastHeartbeatSentFrom = newPosition.getBinlogPosition(); } } } public synchronized void setPosition(Position p) { if ( position == null || p.newerThan(position) ) { position = p; if (storedPosition == null) { storedPosition = p; } } } public synchronized Position getPosition() throws SQLException { if ( position != null ) return position; position = store.get(); return position; } }