/* * 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 com.sun.jini.norm; import java.lang.ref.WeakReference; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import com.sun.jini.collection.WeakTable; import com.sun.jini.landlord.LeasedResource; import com.sun.jini.thread.InterruptedStatusThread; import com.sun.jini.thread.WakeupManager; /** * Lease manager that aggressively expires leases as their expiration times * occur. Also schedules and manages expiration warning events. * <p> * Note, unlike Mahalo's <code>LeaseExpirationManager</code> (which this * was seeded from), we make no attempt to make it generic because of * the need to schedule expiration warning events. * * @author Sun Microsystems, Inc. */ class LeaseExpirationMgr implements WeakTable.KeyGCHandler { /** Logger for logging messages for this class */ static final Logger logger = Logger.getLogger("com.sun.jini.norm"); /** * Map of sets to task tickets. * <p> * A Note on Synchronization * <p> * Whenever we operate on the <code>ticketMap</code> we hold * the lock on the key being used. This is necessary because * expiration and warning sender tasks need to remove tickets from * the map but at the same time a renewal may be updating the map * to associate the set with a new ticket. If we don't synchronize * there is a small window where a task could remove the ticket * for its replacement. */ private WeakTable ticketMap = new WeakTable(this); /** Ref to the main server object has all the top level methods */ private NormServerBaseImpl server; /** Queue of tasks, ordered by time */ private WakeupManager runQueue = new WakeupManager(); /** Queue of tasks to expire sets */ final List expireQueue = new LinkedList(); /** Thread to expire sets */ private final Thread expireThread = new ExpirationThread(); /** * Create a <code>LeaseExpirationMgr</code> to aggressively expire * the leases of the passed <code>NormServerBaseImpl</code> */ LeaseExpirationMgr(NormServerBaseImpl server) { this.server = server; expireThread.start(); } /** * Terminate the <code>LeaseExpirationMgr</code>, killing any * threads it has started */ void terminate() { runQueue.stop(); runQueue.cancelAll(); expireThread.interrupt(); } /** * Notifies the manager of a new lease being created. * * @param resource the resource associated with the new lease */ void register(LeasedResource resource) { // Need to synchronize because schedule manipulates // ticketMap. synchronized (resource) { schedule(resource); } } /** * Notifies the manager of a lease being renewed. <p> * * This method assumes the lock on <code>set</code> is owned by the * current thread. * * @param resource the set for which tasks have to be rescheduled */ void reschedule(LeasedResource resource) { /* * Remove the old event. This method is only called * (indirectly) from NormServerBaseImpl.renew() so we know that * we own the lock on resource. */ WakeupManager.Ticket ticket = (WakeupManager.Ticket) ticketMap.remove(resource); if (ticket != null) { runQueue.cancel(ticket); } // Schedule the new event schedule(resource); } /** * Schedule a leased resource to be reaped in the future. Called * when a resource gets a lease, a lease is renewed, and during log * recovery. * <p> * This method assumes the lock on <code>resource</code> is owned by * the current thread. */ void schedule(LeasedResource resource) { WakeupManager.Ticket ticket; final LeaseSet set = (LeaseSet) resource; MgrTask task; if (set.haveWarningRegistration()) { task = new SendWarning(set); ticket = runQueue.schedule(set.getWarningTime(), task); } else { task = new QueueExpiration(set); ticket = runQueue.schedule(set.getExpiration(), task); } /* * No window here because the tasks only use the ticket after * they acquire the lock on their set, but we still own the lock * on the set. */ task.setTicket(ticket); ticketMap.getOrAdd(set, ticket); } // purposefully inherit doc comment from supertype // Called when LeaseResource we are tracking is garbage collected public void keyGC(Object value) { final WakeupManager.Ticket ticket = (WakeupManager.Ticket) value; runQueue.cancel(ticket); } /** * Expires sets queued for expiration. Perform the expiration in a * separate thread because the operation will block if a snapshot is going * on. It's OK for an expiration to block other expirations, which need * not be timely, but using the separate thread avoids blocking renewal * warnings, which should be timely. */ private class ExpirationThread extends InterruptedStatusThread { ExpirationThread() { super("expire lease sets thread"); setDaemon(true); } public void run() { while (!hasBeenInterrupted()) { try { Runnable task; synchronized (expireQueue) { if (expireQueue.isEmpty()) { expireQueue.wait(); continue; } task = (Runnable) expireQueue.remove(0); } task.run(); } catch (InterruptedException e) { return; } catch (Throwable t) { logger.log(Level.INFO, "Exception in lease set expiration thread -- " + "attempting to continue", t); } } } } /** * Utility base class for our tasks, mainly provides the the proper * locking for manipulating the ticketMap. */ private abstract class MgrTask implements Runnable { /** Resource this task is to operate on */ protected final WeakReference resourceRef; /** Ticket for this task */ private WakeupManager.Ticket ticket; /** * Simple constructor. * * @param set the set this task is to operate on */ protected MgrTask(LeaseSet set) { resourceRef = new WeakReference(set); } /** Set the ticket associated with this task. */ private void setTicket(WakeupManager.Ticket ticket) { this.ticket = ticket; } /** * Removes this task's ticket from the ticket map iff this * task's ticket is in the map. Returns the * <code>LeaseSet</code> this task is to operate on or * <code>null</code> if this task should stop. */ protected LeaseSet removeOurTicket() { final LeaseSet set = (LeaseSet) resourceRef.get(); if (set != null) { synchronized (set) { final WakeupManager.Ticket currentTicket = (WakeupManager.Ticket) ticketMap.get(set); if (ticket.equals(currentTicket)) { ticketMap.remove(set); } else { /* * Someone removed us after we were committed to * run -- we should stop. */ return null; } } } return set; } // purposefully inherit doc comment from supertype public abstract void run(); } /** Task that queues a task to expire a lease set. */ private class QueueExpiration extends MgrTask { QueueExpiration(LeaseSet set) { super(set); } public void run() { LeaseSet set = removeOurTicket(); if (set != null) { synchronized (expireQueue) { expireQueue.add(new Expiration(set)); expireQueue.notifyAll(); } } } } /** * Objects that do the actual expiration of the set in question, * stuck in <code>expireQueue</code>. */ private class Expiration implements Runnable { private LeaseSet set; /** * Create a <code>Expiration</code> task for the passed resource. * * @param set the set this task is to operate on */ private Expiration(LeaseSet set) { this.set = set; } // purposefully inherit doc comment from supertype public void run() { server.expireIfTime(set); /* * Note we don't care if it's actually time or not, if it * is not the task will be rescheduled by the renewal. */ } } /** * Objects that do the schedule the warning events, also schedules * an expiration task. */ private class SendWarning extends MgrTask { /** * Create a <code>SendWarning</code> task for the passed resource. * * @param set the set this task is to operate on */ private SendWarning(LeaseSet set) { super(set); } // purposefully inherit doc comment from supertype public void run() { final LeaseSet s = (LeaseSet) resourceRef.get(); if (s == null) { // set is gone, no work to do return; } /* * By holding this lock we prevent other threads from * scheduling new tasks for this set...if we have been * replaced we will return before scheduling a new task, if * we have not been we will schedule the new task and it can * be cleanly removed by any renew that is happening at the * same time. */ synchronized (s) { final LeaseSet set = removeOurTicket(); if (set == null) { // set is gone, or our task was replaced, no work to do return; } // Send event server.sendWarningEvent(set); // Schedule expiration task final MgrTask task = new QueueExpiration(set); final WakeupManager.Ticket newTicket = runQueue.schedule(set.getExpiration(), task); task.setTicket(newTicket); ticketMap.getOrAdd(set, newTicket); } } } }