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

net.snowflake.client.core.HeartbeatBackground Maven / Gradle / Ivy

/*
 * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
 */

package net.snowflake.client.core;

import java.util.HashSet;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import net.snowflake.client.log.SFLogger;
import net.snowflake.client.log.SFLoggerFactory;

/**
 * This class is a singleton which is running inside driver to heartbeat snowflake server for each
 * connection
 */
public class HeartbeatBackground implements Runnable {
  private static HeartbeatBackground singleton = new HeartbeatBackground();

  /** The logger. */
  private static final SFLogger LOGGER = SFLoggerFactory.getLogger(HeartbeatBackground.class);

  // default master token validity (in seconds) is 4 hours
  private long masterTokenValidityInSecs = 4 * 3600;

  /** How often heartbeat executes is calculated based on master token validity and the headroom */
  private long heartBeatIntervalInSecs = masterTokenValidityInSecs / 4;

  // Scheduler handling the main heartbeat background
  private ScheduledExecutorService scheduler = null;

  // future for a scheduled heartbeat task
  ScheduledFuture heartbeatFuture;

  /**
   * List of sessions to heartbeat. Use weak hash map so that if a session object is deleted and
   * garbaged collected, it will be removed from the list so that we will not keep doing heartbeat
   * for it. This is to take care of the case when some application does not close session before it
   * goes out of scope.
   */
  WeakHashMap sessions = new WeakHashMap<>();

  // When is the last time heartbeat started
  private long lastHeartbeatStartTimeInSecs = 0;

  // Method to get the heartbeat instance
  public static HeartbeatBackground getInstance() {
    return singleton;
  }

  /** private constructor so that no one can try to create one */
  private HeartbeatBackground() {}

  /**
   * Method to add a session
   *
   * 

It will compare the master token validity and stored master token validity and if it is * less, we will update the stored one and the heartbeat interval and reschedule heartbeat. * *

This method is called when a session is created. * * @param session the session will be added * @param masterTokenValidityInSecs time interval for which client need to check validity of * master token with server */ protected synchronized void addSession( SFSession session, long masterTokenValidityInSecs, int heartbeatFrequencyInSecs) { boolean requireReschedule = false; long oldHeartBeatIntervalInSecs = this.heartBeatIntervalInSecs; this.heartBeatIntervalInSecs = (long) heartbeatFrequencyInSecs; if (this.heartBeatIntervalInSecs > masterTokenValidityInSecs / 4) { this.heartBeatIntervalInSecs = masterTokenValidityInSecs / 4; } LOGGER.debug( "update heartbeat interval" + " from {} to {}", oldHeartBeatIntervalInSecs, this.heartBeatIntervalInSecs); // heartbeat rescheduling required requireReschedule = true; // add session to the list to be heartbeated sessions.put(session, Boolean.TRUE); /* * Create scheduler if it is the first time. It uses a custom thread * factory that will create daemon thread so that it will not block * JVM from exiting. */ if (this.scheduler == null) { LOGGER.debug("create heartbeat thread pool", false); this.scheduler = Executors.newScheduledThreadPool( 1, new ThreadFactory() { @Override public Thread newThread(Runnable runnable) { Thread thread = Executors.defaultThreadFactory().newThread(runnable); thread.setName("heartbeat (" + thread.getId() + ")"); thread.setDaemon(true); return thread; } }); } // schedule a heartbeat task if none exists if (heartbeatFuture == null) { LOGGER.debug("schedule heartbeat task", false); this.scheduleHeartbeat(); } // or reschedule if the master token validity has been reduced (rare event) else if (requireReschedule) { LOGGER.debug("Cancel existing heartbeat task", false); // Cancel existing task if not started yet and reschedule if (heartbeatFuture.cancel(false)) { LOGGER.debug("Canceled existing heartbeat task, reschedule", false); this.scheduleHeartbeat(); } else { LOGGER.debug("Failed to cancel existing heartbeat task", false); } } } /** * Method to remove a session. This is called when a session is closed. Notice that if a session * is not closed but the session object goes out of scope, then this method will not be called. * And then the session will be kept in the list of sessions to be heartbeated until the object * gets garbage collected since we use weak reference to the object. * * @param session the session will be removed */ protected synchronized void removeSession(SFBaseSession session) { sessions.remove(session); } /** Schedule the next heartbeat */ private void scheduleHeartbeat() { // elapsed time in seconds since the last heartbeat long elapsedSecsSinceLastHeartBeat = System.currentTimeMillis() / 1000 - lastHeartbeatStartTimeInSecs; /* * The initial delay for the new scheduling is 0 if the elapsed * time is more than the heartbeat time interval, otherwise it is the * difference between the heartbeat time interval and the elapsed time. */ long initialDelay = Math.max(heartBeatIntervalInSecs - elapsedSecsSinceLastHeartBeat, 0); LOGGER.debug("schedule heartbeat task with initial delay of {} seconds", initialDelay); // Creates and executes a periodic action to send heartbeats this.heartbeatFuture = this.scheduler.schedule(this, initialDelay, TimeUnit.SECONDS); } /** * Run heartbeat: for each session send a heartbeat request and schedule next heartbeat as long as * there are sessions left. * *

Notice that the synchronization is only around the code that visits the global sessions map * and the code that schedules next heartbeat, but not around the heartbeat calls for each session * in order to minimize the chance of blocking the adding of a session by performing the * heartbeats. This is because adding a session is called from JDBC connection creation call which * directly affects application performance. */ @Override public void run() { /* * Remember current time as the heartbeat start time. This is used for * calculating the delay for the next heartbeat. */ this.lastHeartbeatStartTimeInSecs = System.currentTimeMillis() / 1000; Set sessionsToHeartbeat = new HashSet(); // synchronously get a copy of the sessions from the global list synchronized (this) { sessionsToHeartbeat.addAll(sessions.keySet()); } // heartbeat every session. for (SFSession session : sessionsToHeartbeat) { try { session.heartbeat(); } catch (Throwable ex) { LOGGER.error("heartbeat error - message=" + ex.getMessage(), ex); } } /* * The following is synchronized with the methods to add or remove a * session so that we always make sure we have one heartbeat task scheduled * when there is any session left. */ synchronized (this) { // schedule next heartbeat if (sessions.size() > 0) { LOGGER.debug("schedule next heartbeat run", false); scheduleHeartbeat(); } else { LOGGER.debug("no need for heartbeat since no more sessions", false); // no need to heartbeat if no more session this.heartbeatFuture = null; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy