org.apache.river.norm.LeaseSet Maven / Gradle / Ivy
/*
* 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.river.norm;
import org.apache.river.landlord.LeasedResource;
import org.apache.river.norm.event.EventFactory;
import org.apache.river.norm.event.EventType;
import org.apache.river.norm.event.EventTypeGenerator;
import org.apache.river.norm.event.SendMonitor;
import org.apache.river.norm.proxy.*;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.rmi.MarshalledObject;
import java.security.AccessControlContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lease.Lease;
import net.jini.export.ProxyAccessor;
import net.jini.id.Uuid;
import net.jini.io.MarshalledInstance;
import net.jini.lease.ExpirationWarningEvent;
import net.jini.lease.LeaseRenewalSet;
import net.jini.security.ProxyPreparer;
import org.apache.river.api.io.AtomicSerial;
import org.apache.river.api.io.AtomicSerial.GetArg;
/**
* Norm's internal representation of LeaseRenewalSets. Unless otherwise
* noted the methods of this class assume synchronization being handled
* by the caller.
*
* @author Sun Microsystems, Inc.
*/
@AtomicSerial
class LeaseSet implements Serializable, LeasedResource {
private static final long serialVersionUID = 2;
/**
* Expiration time of the set
* @serial
*/
private long expiration;
/**
* We keep two copies of the expiration time guarded by different
* locks so the the client lease renewal threads don't have to wait
* on the set's lock.
*/
private transient ExpirationTime expiration2;
/**
* ID that uniquely identifies this set
* @serial
*/
private final Uuid ID;
/**
* A collection of the client leases in this set (in wrapped form).
* @serial
*/
private final Set leases;
/**
* A table that maps client leases to client lease wrappers.
*/
private volatile transient LeaseTable leaseTable;
/**
* Time before expiration
to send expiration warning events
* @serial
*/
private volatile long minWarning = NormServer.NO_LISTENER;
/**
* The event type for expiration warning events
* @serial
*/
private EventType warningEventType;
/**
* The current sequence number for expiration warning events
* @serial
*/
private volatile long warningSeqNum;
/**
* The event type for failure events
* @serial
*/
private EventType failureEventType;
/**
* PersistentStore
that changes should be logged to.
*/
private transient PersistentStore store;
/**
* The NormServerBaseImpl
are attached to
*/
private transient NormServerBaseImpl normServerBaseImpl;
// Constructors and state restoration
/**
* Simple constructor. Note expiration will be set when we allocate
* a lease for this set.
* @param ID for this set
* @param generator object set can use to create new event type objects
* @param store PersistentStore that changes should be logged
* to
* @param normServerBaseImpl the NormServerBaseImpl
that
* created this set
*/
LeaseSet(Uuid ID,
EventTypeGenerator generator,
PersistentStore store,
NormServerBaseImpl normServerBaseImpl,
AccessControlContext context)
{
this.leases = new HashSet();
this.store = store;
this.normServerBaseImpl = normServerBaseImpl;
this.ID = ID;
// For completeness
expiration = 0;
expiration2 = new ExpirationTime(expiration);
leaseTable = new LeaseTable();
final SendMonitor sendMonitor =
normServerBaseImpl.newSendMonitor(this);
try {
warningEventType =
generator.newEventType(sendMonitor,
LeaseRenewalSet.EXPIRATION_WARNING_EVENT_ID, context);
failureEventType =
generator.newEventType(sendMonitor,
LeaseRenewalSet.RENEWAL_FAILURE_EVENT_ID, context);
} catch (IOException e) {
// Because we are passing null for the listener we will
// never get an exception
throw new AssertionError();
}
}
LeaseSet(GetArg arg) throws IOException {
this( arg.get("expiration", 0L),
(Uuid) arg.get("ID", null),
check((Set) arg.get("leases", null)),
arg.get("minWarning", 0L),
(EventType) arg.get("warningEventType", null),
arg.get("warningSeqNum", 0L),
(EventType) arg.get("failureEventType", null)
);
// Restore the 2nd copy
expiration2 = new ExpirationTime(expiration);
// Create lease table, but only populate after restoring lease wrapper
// transient state
leaseTable = new LeaseTable();
}
LeaseSet(long expiration,
Uuid ID,
Set leases,
long minWarning,
EventType warningEventType,
long warningSeqNum,
EventType failureEventType)
{
this.expiration = expiration;
this.ID = ID;
this.leases = new HashSet(leases);
this.minWarning = minWarning;
this.warningEventType = warningEventType;
this.warningSeqNum = warningSeqNum;
this.failureEventType = failureEventType;
}
private static Set check(Set leases)
throws InvalidObjectException
{
try {
Set checkLeases = Collections.checkedSet(
new HashSet(leases.size()), ClientLeaseWrapper.class);
checkLeases.addAll(leases);
return checkLeases;
} catch (ClassCastException e){
InvalidObjectException ex = new InvalidObjectException(
"leases must only contain instances of ClientLeaseWrapper");
ex.initCause(e);
throw ex;
}
}
/**
* Override readObject so we can restore expiration2
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
synchronized (this){
// Restore the 2nd copy
expiration2 = new ExpirationTime(expiration);
// Create lease table, but only populate after restoring lease wrapper
// transient state
leaseTable = new LeaseTable();
}
}
/**
* Restore the transient state of the set that can't be restored
* automatically after a log recovery.
* @param generator event type generator associated with this set's events
* @param store PersistentStore that changes should be
* logged to
* @param normServerBaseImpl the normServerBaseImpl
that
* created this set
* @param recoveredListenerPreparer the proxy preparer to use to prepare
* recovered listeners
* @return an iterator over the set of client leases
*/
Iterator restoreTransientState(EventTypeGenerator generator,
PersistentStore store,
NormServerBaseImpl normServerBaseImpl,
ProxyPreparer recoveredListenerPreparer,
AccessControlContext context)
{
this.normServerBaseImpl = normServerBaseImpl;
this.store = store;
final SendMonitor sendMonitor =
normServerBaseImpl.newSendMonitor(this);
warningEventType.restoreTransientState(
generator, sendMonitor, recoveredListenerPreparer, context);
failureEventType.restoreTransientState(
generator, sendMonitor, recoveredListenerPreparer, context);
// Instead of logging the sequence number each time we send
// a warning event (like we do for renewal failures), we just
// record the current sequence number as of every snapshot and
// assume we won't send more than Integer.MAX_VALUE warning events
// between snapshots, nor will we crash and restart more than
// ~ Long.MAX_VALUE/Integer.MAX_VALUE times.
warningEventType.setLastSequenceNumber(warningSeqNum +
Integer.MAX_VALUE);
normServerBaseImpl.updateLeaseCount(leases.size());
return leases.iterator();
}
/**
* Return the wrapper for the specified client lease, or null if not
* found.
*/
ClientLeaseWrapper getClientLeaseWrapper(Lease clientLease) {
return leaseTable.get(clientLease);
}
/**
* Utility method to replace a client lease in the lease set. Returns true
* if an equal lease was not already present. Does not update the lease
* table.
*/
private boolean replace(ClientLeaseWrapper clw) {
boolean found = leases.remove(clw);
leases.add(clw);
return !found;
}
/**
* Add or update the specified wrapped client lease to the set.
* @param clw lease to be added or updated
*/
void update(ClientLeaseWrapper clw) {
boolean added = replace(clw);
leaseTable.put(clw);
final Object u = new UpdateClientLease(this, clw);
store.update(u);
if (added) {
normServerBaseImpl.updateLeaseCount(1);
}
}
/**
* Add a lease already in the lease set to the lease table. Used during
* recovery to add the wrapper to the lease table only after it has been
* given its recovery proxy preparer and attempting to unpack the lease has
* attempted.
*/
void addToLeaseTable(ClientLeaseWrapper clw) {
leaseTable.put(clw);
}
/**
* Return true if the passed wrapper is in the set
*/
boolean doesContainWrapper(ClientLeaseWrapper clw) {
return leases.contains(clw);
}
/**
* Utility method to remove a client lease. Returns true if the lease is
* removed.
*/
private boolean removeInternal(ClientLeaseWrapper clw) {
if (leases.remove(clw)) {
leaseTable.remove(clw);
return true;
}
return false;
}
/**
* Remove the specified wrapped client lease from the set.
* @param clw lease to removed
* @return false if the lease has already been removed, or
* if logically the lease is no longer in the set (e.g. its
* membership expiration has been reached)
*/
boolean remove(ClientLeaseWrapper clw) {
if (!removeInternal(clw)) {
// if we are here the lease must have already been
// removed by some other thread, don't bother logging
return false;
}
// Even if the membership expiration was expired we still
// want to update the log
final Object u = new RemoveClientLease(this, clw);
store.update(u);
normServerBaseImpl.updateLeaseCount(-1);
return (clw.getMembershipExpiration() > System.currentTimeMillis());
}
/**
* Destroy a lease set
* @return a Set
with all of the sets WrappedClientLeases
*/
Set destroy() {
setExpiration(-1);
final Object u = new CancelLeaseSet(getUuid());
store.update(u);
normServerBaseImpl.updateLeaseCount(-leases.size());
return leases;
}
/**
* Return an array of leases in marshalled form. For each lease we have
* in unmarshalled form serialize using the duration format, for each
* lease we can't unmarshal just use the MarshalledInstance we have on
* hand. If the set is empty return null.
*/
MarshalledInstance[] getLeases() {
final long now = System.currentTimeMillis();
final Iterator i = leases.iterator();
final List l = new ArrayList(leases.size());
while (i.hasNext()) {
final ClientLeaseWrapper clw = (ClientLeaseWrapper) i.next();
// Check to make sure the leases membership expiration has not
// passed
if (now > clw.getMembershipExpiration())
continue;
l.add(clw.getMarshalledClientLease());
}
if (l.isEmpty()) {
return null;
} else {
return (MarshalledInstance[]) l.toArray(
new MarshalledInstance[l.size()]);
}
}
/**
* Set/update/clear the expiration warning listener.
* @param listener the new listener
* @param minWarning how long before the lease on the set expires should
* the event be sent
* @param handback the new handback
* @return if listener
is non-null
return
* an EventRegistration
otherwise return null
* @throws IOException if listener cannot be serialized
*/
EventRegistration setExpirationWarningListener(
RemoteEventListener listener,
long minWarning,
MarshalledObject handback)
throws IOException
{
synchronized (this){
this.minWarning = minWarning;
}
warningEventType.setListener(listener, handback);
final Object u = new WarningEventRegistration(this);
store.update(u);
if (listener == null)
return null;
final SetProxy proxy = newSetProxy();
return new EventRegistration(
warningEventType.getEventID(),
proxy,
proxy.getRenewalSetLease(),
warningEventType.getLastSequenceNumber());
}
/**
* Set/update/clear the renewal failure listener
* @param listener the new listener
* @param handback the new handback
* @return if listener
is non-null
return
* an EventRegistration
otherwise return null
* @throws IOException if listener can not be serialized
*/
EventRegistration setRenewalFailureListener(
RemoteEventListener listener,
MarshalledObject handback)
throws IOException
{
failureEventType.setListener(listener, handback);
final Object u = new FailureEventRegistration(this);
store.update(u);
if (listener == null)
return null;
final SetProxy proxy = newSetProxy();
return new EventRegistration(
failureEventType.getEventID(),
proxy,
proxy.getRenewalSetLease(),
failureEventType.getLastSequenceNumber());
}
/**
* Handle failures to renew a lease by removing the lease from the set
* and if needed schedule sending an event.
* @param clw the wrapped client lease for the lease that could not
* be removed
*/
void renewalFailure(ClientLeaseWrapper clw) {
if (!removeInternal(clw)) {
// If we are here the lease must have already been
// removed by some other thread, don't bother logging or
// sending an event.
return;
}
Object u;
EventFactory factory;
long seqNum;
try {
factory = clw.newFailureFactory(newSetProxy());
seqNum = failureEventType.sendEvent(factory);
} catch (IOException e) {
// We need to log the event even if we can't send it.
// Update the sequence number since an event has
// occurred. Note, we only get here if creating the
// factory fails (failureEventType.sendEvent() does not
// throw IOException) so we there is no danger of
// incrementing the sequence number twice
// $$$ is this the right thing to do?
seqNum = failureEventType.bumpSequenceNumber();
}
u = new RenewalFailure(this, clw, seqNum);
store.update(u);
}
/**
* Send an expiration warning event for this set
*/
void sendWarningEvent() {
warningSeqNum = warningEventType.sendEvent(new WarningFactory(this));
}
/**
* Nested class that implements EventFactory
that
* generates ExpirationWarningEvent
s.
*/
private static class WarningFactory implements EventFactory {
private static final long serialVersionUID = 1L; // Not Serializable
/** The source for the event */
final private SetProxy proxy;
/**
* Create a new WarningFactory
* @param set The set generating this event
*/
WarningFactory(LeaseSet set) {
proxy = set.newSetProxy();
}
// Inherit java doc from super type
public RemoteEvent createEvent(long eventID,
long seqNum,
MarshalledObject handback)
{
return new ExpirationWarningEvent(proxy, seqNum, handback);
}
}
/**
* Create a new SetProxy for this set that has a lease with the
* current expiration
*/
private SetProxy newSetProxy() {
return normServerBaseImpl.newSetProxy(this);
}
/**
* Return true if there is a non-null
listener registered
* for the expiration warning event.
*
* Note, this method assumes the current thread owns the set's lock
*/
boolean haveWarningRegistration() {
return warningEventType.haveListener();
}
/**
* Return the absolute time when a expiration warning should be sent.
*/
synchronized long getWarningTime() {
return expiration - minWarning;
}
/**
* Log the renewal of a client lease.
* @param clw the wrapper for the client lease that was renewed
*/
void logRenewal(ClientLeaseWrapper clw) {
if (!leases.contains(clw)) {
// Some other thread must have removed this lease from
// the set after renewal, don't bother logging change
return;
}
final Object u = new UpdateClientLease(this, clw);
store.update(u);
}
// Methods need to meet contract of LeasedResource
// Inherit java doc from super type
public synchronized void setExpiration(long newExpiration) {
expiration = newExpiration;
// Update the 2nd copy
expiration2.set(expiration);
}
// Inherit java doc from super type
public synchronized long getExpiration() {
return expiration;
}
/**
* This method is used by the client lease renewal threads to make
* sure that the set associated with the lease they are renewing is
* non-expired. Note, the method check expiration2, not expiration
* so the renewal thread does not need to block on the set's lock.
* @param now the current time in milliseconds since the beginning of
* the epoch
*/
boolean ensureCurrent(long now) {
return expiration2.ensureCurrent(now);
}
// Inherit java doc from super type
public Uuid getCookie() {
return ID;
}
/**
* Return the Uuid
for this set. */
Uuid getUuid() {
return ID;
}
/**
* If the passed registrationNumber number matches the
* current registrationNumber for the passed event
* clear the current registration and persist the change
*/
void definiteException(EventType type, RemoteEvent ev,
long registrationNumber)
{
final boolean changed =
type.clearListenerIfSequenceMatch(registrationNumber);
if (changed) {
// Need to log the change
Object u;
if (ev instanceof ExpirationWarningEvent) {
u = new WarningEventRegistration(LeaseSet.this);
} else {
u = new FailureEventRegistration(LeaseSet.this);
}
store.update(u);
}
}
/**
* Returns whether to isolate renewal sets or batch leases across sets for
* all lease renewal sets associated with this set's service.
*/
protected boolean isolateSets() {
return normServerBaseImpl.isolateSets();
}
/**
* Returns a string representation of this object.
*/
public String toString() {
return "LeaseSet" + ID;
}
// Inner classes
/**
* Utility class that holds and guards the second copy of our expiration
* time.
*/
private static class ExpirationTime {
private static final long serialVersionUID = 1L; // Not Serializable
/**
* The expiration time in milliseconds since the beginning of the
* epoch
*/
private long expirationTime;
/** Simple constructor */
private ExpirationTime(long initVal) {
expirationTime = initVal;
}
/** Update the current expiration time */
private synchronized void set(long newTime) {
expirationTime = newTime;
}
/**
* Return true if expiration time has not been reached
* @param now the current time in milliseconds since the beginning of
* the epoch
*/
private synchronized boolean ensureCurrent(long now) {
return now <= expirationTime;
}
}
// All the inner classes we use to log changes to a lease set
/**
* Class used to log changes to the set expiration time.
*/
static class ChangeSetExpiration extends LeaseSetOperation {
private static final long serialVersionUID = 1L;
/**
* Updated expiration time
* @serial
*/
private final long expiration;
/**
* Simple constructor
* @param set that changed
* @param expiration the new expiration time
*/
ChangeSetExpiration(LeaseSet set, long expiration) {
super(set.getUuid());
this.expiration = expiration;
}
// Inherit java doc from super type
void apply(LeaseSet set) {
set.setExpiration(expiration);
}
}
/**
* Class used to log adding or updating a client lease to the set
*/
private static class UpdateClientLease extends LeaseSetOperation {
private static final long serialVersionUID = 1L;
/**
* Wrapped version of client lease
* @serial
*/
private final ClientLeaseWrapper clw;
/**
* Simple constructor
* @param set that changed
* @param clw Wrapped client lease
*/
private UpdateClientLease(LeaseSet set, ClientLeaseWrapper clw) {
super(set.getUuid());
this.clw = clw;
}
// Inherit java doc from super type
void apply(LeaseSet set) {
set.replace(clw);
}
}
/**
* Class used to log the removal of a client lease from the set
*/
private static class RemoveClientLease extends LeaseSetOperation {
private static final long serialVersionUID = 1L;
/**
* Client lease to be removed
* @serial
*/
private final ClientLeaseWrapper clw;
/**
* Simple constructor
* @param set that changed
* @param clw Wrapped client lease
*/
private RemoveClientLease(LeaseSet set, ClientLeaseWrapper clw) {
super(set.getUuid());
this.clw = clw;
}
// Inherit java doc from super type
void apply(LeaseSet set) {
set.leases.remove(clw);
}
}
/**
* Class used to log a renewal failure
*/
private static class RenewalFailure extends RemoveClientLease {
private static final long serialVersionUID = 1L;
/**
* Event ID of the corresponding renewal failure event (if any)
* @serial
*/
private long evID;
/**
* Simple constructor
* @param set that changed
* @param clw Wrapped client lease
* @param evID event ID of the renewal event that was sent
* to mark this renewal failure
*/
private RenewalFailure(LeaseSet set, ClientLeaseWrapper clw,
long evID)
{
super(set, clw);
this.evID = evID;
}
// Inherit java doc from super type
void apply(LeaseSet set) {
super.apply(set);
set.failureEventType.setLastSequenceNumber(evID);
}
}
/**
* Class used to log changes to warning event registrations
*/
private static class WarningEventRegistration extends LeaseSetOperation {
private static final long serialVersionUID = 1L;
/**
* Warning time associated with warning event registration
* @serial
*/
private final long warningTime;
/**
* EventType object that resulted from registration change
* @serial
*/
private final EventType registration;
/**
* Simple constructor
* @param set that changed
*/
private WarningEventRegistration(LeaseSet set) {
super(set.getUuid());
synchronized (set){
warningTime = set.minWarning;
registration = set.warningEventType;
}
}
// Inherit java doc from super type
void apply(LeaseSet set) throws StoreException {
synchronized (set){
set.minWarning = warningTime;
set.warningEventType = registration;
}
}
}
/**
* Class used to log changes to failure event registrations
*/
private static class FailureEventRegistration extends LeaseSetOperation {
private static final long serialVersionUID = 1L;
/**
* EventType object that resulted from registration change
* @serial
*/
private final EventType registration;
/**
* Simple constructor
* @param set that changed
*/
private FailureEventRegistration(LeaseSet set) {
super(set.getUuid());
registration = set.failureEventType;
}
// Inherit java doc from super type
void apply(LeaseSet set) throws StoreException {
set.failureEventType = registration;
}
}
}