/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.zookeeper.server.quorum; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.apache.zookeeper.KeeperException.SessionExpiredException; import org.apache.zookeeper.KeeperException.SessionMovedException; import org.apache.zookeeper.KeeperException.UnknownSessionException; import org.apache.zookeeper.server.SessionTrackerImpl; import org.apache.zookeeper.server.ZooKeeperServerListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The learner session tracker is used by learners (followers and observers) to * track zookeeper sessions which may or may not be echoed to the leader. When * a new session is created it is saved locally in a wrapped * LocalSessionTracker. It can subsequently be upgraded to a global session * as required. If an upgrade is requested the session is removed from local * collections while keeping the same session ID. It is up to the caller to * queue a session creation request for the leader. * A secondary function of the learner session tracker is to remember sessions * which have been touched in this service. This information is passed along * to the leader with a ping. */ public class LearnerSessionTracker extends UpgradeableSessionTracker { private static final Logger LOG = LoggerFactory.getLogger(LearnerSessionTracker.class); private final SessionExpirer expirer; // Touch table for the global sessions private final AtomicReference<Map<Long, Integer>> touchTable = new AtomicReference<Map<Long, Integer>>(); private final long serverId; private final AtomicLong nextSessionId = new AtomicLong(); private final boolean localSessionsEnabled; private final ConcurrentMap<Long, Integer> globalSessionsWithTimeouts; public LearnerSessionTracker(SessionExpirer expirer, ConcurrentMap<Long, Integer> sessionsWithTimeouts, int tickTime, long id, boolean localSessionsEnabled, ZooKeeperServerListener listener) { this.expirer = expirer; this.touchTable.set(new ConcurrentHashMap<Long, Integer>()); this.globalSessionsWithTimeouts = sessionsWithTimeouts; this.serverId = id; nextSessionId.set(SessionTrackerImpl.initializeNextSession(serverId)); this.localSessionsEnabled = localSessionsEnabled; if (this.localSessionsEnabled) { createLocalSessionTracker(expirer, tickTime, id, listener); } } public void removeSession(long sessionId) { if (localSessionTracker != null) { localSessionTracker.removeSession(sessionId); } globalSessionsWithTimeouts.remove(sessionId); touchTable.get().remove(sessionId); } public void start() { if (localSessionTracker != null) { localSessionTracker.start(); } } public void shutdown() { if (localSessionTracker != null) { localSessionTracker.shutdown(); } } public boolean isGlobalSession(long sessionId) { return globalSessionsWithTimeouts.containsKey(sessionId); } public boolean addGlobalSession(long sessionId, int sessionTimeout) { boolean added = globalSessionsWithTimeouts.put(sessionId, sessionTimeout) == null; if (localSessionsEnabled && added) { // Only do extra logging so we know what kind of session this is // if we're supporting both kinds of sessions LOG.info("Adding global session 0x" + Long.toHexString(sessionId)); } touchTable.get().put(sessionId, sessionTimeout); return added; } public boolean addSession(long sessionId, int sessionTimeout) { boolean added; if (localSessionsEnabled && !isGlobalSession(sessionId)) { added = localSessionTracker.addSession(sessionId, sessionTimeout); // Check for race condition with session upgrading if (isGlobalSession(sessionId)) { added = false; localSessionTracker.removeSession(sessionId); } else if (added) { LOG.info("Adding local session 0x" + Long.toHexString(sessionId)); } } else { added = addGlobalSession(sessionId, sessionTimeout); } return added; } public boolean touchSession(long sessionId, int sessionTimeout) { if (localSessionsEnabled) { if (localSessionTracker.touchSession(sessionId, sessionTimeout)) { return true; } if (!isGlobalSession(sessionId)) { return false; } } touchTable.get().put(sessionId, sessionTimeout); return true; } public Map<Long, Integer> snapshot() { return touchTable.getAndSet(new ConcurrentHashMap<Long, Integer>()); } public long createSession(int sessionTimeout) { if (localSessionsEnabled) { return localSessionTracker.createSession(sessionTimeout); } return nextSessionId.getAndIncrement(); } public void checkSession(long sessionId, Object owner) throws SessionExpiredException, SessionMovedException { if (localSessionTracker != null) { try { localSessionTracker.checkSession(sessionId, owner); return; } catch (UnknownSessionException e) { // Check whether it's a global session. We can ignore those // because they are handled at the leader, but if not, rethrow. // We check local session status first to avoid race condition // with session upgrading. if (!isGlobalSession(sessionId)) { throw new SessionExpiredException(); } } } } public void setOwner(long sessionId, Object owner) throws SessionExpiredException { if (localSessionTracker != null) { try { localSessionTracker.setOwner(sessionId, owner); return; } catch (SessionExpiredException e) { // Check whether it's a global session. We can ignore those // because they are handled at the leader, but if not, rethrow. // We check local session status first to avoid race condition // with session upgrading. if (!isGlobalSession(sessionId)) { throw e; } } } } public void dumpSessions(PrintWriter pwriter) { if (localSessionTracker != null) { pwriter.print("Local "); localSessionTracker.dumpSessions(pwriter); } pwriter.print("Global Sessions("); pwriter.print(globalSessionsWithTimeouts.size()); pwriter.println("):"); SortedSet<Long> sessionIds = new TreeSet<Long>( globalSessionsWithTimeouts.keySet()); for (long sessionId : sessionIds) { pwriter.print("0x"); pwriter.print(Long.toHexString(sessionId)); pwriter.print("\t"); pwriter.print(globalSessionsWithTimeouts.get(sessionId)); pwriter.println("ms"); } } public void setSessionClosing(long sessionId) { // Global sessions handled on the leader; this call is a no-op if // not tracked as a local session so safe to call in both cases. if (localSessionTracker != null) { localSessionTracker.setSessionClosing(sessionId); } } @Override public Map<Long, Set<Long>> getSessionExpiryMap() { return new HashMap<Long, Set<Long>>(); } }