LeaseManager.java 5.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
/*
 * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package com.sun.jmx.event;

import com.sun.jmx.remote.util.ClassLogger;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * <p>Manage a renewable lease.  The lease can be renewed indefinitely
 * but if the lease runs to its current expiry date without being renewed
 * then the expiry callback is invoked.  If the lease has already expired
 * when renewal is attempted then the lease method returns zero.</p>
 * @author sjiang
 * @author emcmanus
 */
// The synchronization logic of this class is tricky to deal correctly with the
// case where the lease expires at the same time as the |lease| or |stop| method
// is called.  If the lease is active then the field |scheduled| represents
// the expiry task; otherwise |scheduled| is null.  Renewing or stopping the
// lease involves canceling this task and setting |scheduled| either to a new
// task (to renew) or to null (to stop).
//
// Suppose the expiry task runs at the same time as the |lease| method is called.
// If the task enters its synchronized block before the method starts, then
// it will set |scheduled| to null and the method will return 0.  If the method
// starts before the task enters its synchronized block, then the method will
// cancel the task which will see that when it later enters the block.
// Similar reasoning applies to the |stop| method.  It is not expected that
// different threads will call |lease| or |stop| simultaneously, although the
// logic should be correct then too.
public class LeaseManager {
    public LeaseManager(Runnable callback) {
        this(callback, EventParams.getLeaseTimeout());
    }

    public LeaseManager(Runnable callback, long timeout) {
        if (logger.traceOn()) {
            logger.trace("LeaseManager", "new manager with lease: "+timeout);
        }
        if (callback == null) {
            throw new NullPointerException("Null callback.");
        }
        if (timeout <= 0)
            throw new IllegalArgumentException("Timeout must be positive: " + timeout);

        this.callback = callback;
        schedule(timeout);
    }

    /**
     * <p>Renew the lease for the given time.  The new time can be shorter
     * than the previous one, in which case the lease will expire earlier
     * than it would have.</p>
     *
     * <p>Calling this method after the lease has expired will return zero
     * immediately and have no other effect.</p>
     *
     * @param timeout the new lifetime.  If zero, the lease
     * will expire immediately.
     */
    public synchronized long lease(long timeout) {
        if (logger.traceOn()) {
            logger.trace("lease", "new lease to: "+timeout);
        }

        if (timeout < 0)
            throw new IllegalArgumentException("Negative lease: " + timeout);

        if (scheduled == null)
            return 0L;

        scheduled.cancel(false);

        if (logger.traceOn())
            logger.trace("lease", "start lease: "+timeout);
        schedule(timeout);

        return timeout;
    }

    private class Expire implements Runnable {
        ScheduledFuture<?> task;

        public void run() {
            synchronized (LeaseManager.this) {
                if (task.isCancelled())
                    return;
                scheduled = null;
            }
            callback.run();
        }
    }

    private synchronized void schedule(long timeout) {
        Expire expire = new Expire();
        scheduled = executor.schedule(expire, timeout, TimeUnit.MILLISECONDS);
        expire.task = scheduled;
    }

    /**
     * <p>Cancel the lease without calling the expiry callback.</p>
     */
    public synchronized void stop() {
        logger.trace("stop", "canceling lease");
        scheduled.cancel(false);
        scheduled = null;
    }

    private final Runnable callback;
    private ScheduledFuture scheduled;  // If null, the lease has expired.

    private final ScheduledExecutorService executor
            = Executors.newScheduledThreadPool(1,
            new DaemonThreadFactory("LeaseManager"));

    private static final ClassLogger logger =
            new ClassLogger("javax.management.event", "LeaseManager");

}