/*
* 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.outrigger;
import java.util.SortedSet;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Maintain a journal of operations on entries (writes, takes, and
* resolution of locks) and a thread that will process the
* journal. This journal is kept for three reasons. One is so events
* can be generated asynchronously from the writes and transaction
* commits that cause them. Second so blocked queries can be alerted
* to changes that will allow them to be resolved.
* <p>
* Third to make sure that transitions occur between the time a query
* starts its initial search and the time it adds its watcher to the
* <code>TransitionWatchers</code> object do not get missed. This
* last goal is accomplished by queries calling
* <code>newTransitionIterator</code> before initial search, doing the
* initial search, adding their watcher to the
* <code>TransitionWatchers</code> object and then calling
* <code>watcherRegistered</code> on the
* <code>TransitionIterator</code> and then feeding their watcher the
* chain of transitions yielded by the
* <code>TransitionIterator</code>. (skipping any transitions with
* entries that don't match).
* <p>
* Each operation that is recored in the journal is assigned an
* <em>ordinal</em>. Operations with higher ordinals must be considered
* to have taken place after operations with lower ordinals.
*/
class OperationJournal extends Thread {
/** The object to ask about who is interested in a transition */
private final TransitionWatchers watchers;
/** The current tail of the transitions list. */
private JournalNode tail;
/**
* The <code>JournalNode</code> we are currently processing or if
* none are in process the last one we processed.
*/
private JournalNode lastProcessed;
/** If <code>true</code> stop thread */
private boolean dead = false;
/** The last ordinal value used */
private long lastOrdinalUsed = 1;
/** Logger for logging exceptions */
private static final Logger logger =
Logger.getLogger(OutriggerServerImpl.opsLoggerName);
/**
* The nodes of our Journal
*/
private class JournalNode {
/** The next node in the journal */
private JournalNode next;
/** The sequence number of this journal entry */
private final long ordinal;
/** The contents of this journal entry */
private final Object payload;
/**
* Create a new <code>JournalNode</code> with the specified
* value for the <code>payload</code>, <code>null</code> for
* <code>next</code>, and the appropriate value for
* <code>ordinal</code>.
* <p>
* Assumes the lock on the <code>OperationJournal</code> is held,
* @param payload The value for the payload field.
*/
private JournalNode(Object payload) {
ordinal = ++lastOrdinalUsed;
this.payload = payload;
}
/**
* Set the next element in the list.
* @param n the new value for the next field.
* @throws IllegalStateException if the next field has
* already been set.
* @throws NullPointerException if <code>n</code> is
* <code>null</code>.
*/
private synchronized void setNext(JournalNode n) {
if (next != null)
throw new IllegalStateException("Already has next");
if (n == null)
throw new NullPointerException("n must be non-null");
next = n;
}
/**
* Get the next element in the journal.
* @return the next element in the list, or <code>null</code>
* if there is currently no next element.
*/
private synchronized JournalNode getNext() {
return next;
}
}
/**
* <code>JournalNode</code> payload value used for
* caught up markers.
*/
private class CaughtUpMarker {
/** The watcher to notify */
private final IfExistsWatcher watcher;
/**
* Create a new <code>CaughtUpMarker</code> that
* will notify the given <code>watcher</code>.
* @throws NullPointerException if <code>watcher</code> is
* <code>null</code>.
*/
private CaughtUpMarker(IfExistsWatcher watcher) {
if (watcher == null)
throw new NullPointerException("watcher must not be null");
this.watcher = watcher;
}
}
/**
* An iterator that will yield (in the order they were posted) all the
* <code>EntryTransition</code>s added after the iterator was
* created and processed before <code>watcherRegistered</code> was
* called. This call assumes it is only used by a single thread.
*/
class TransitionIterator {
/**
* The place to end, <code>null</code> if
* <code>watcherRegistered</code> has not yet
* been called.
*/
private JournalNode end;
/** Our current position in the journal */
private JournalNode current;
/**
* Create a new <code>TransitionIterator</code> that will
* start with the first <code>EntryTransition</code> that
* appears in the journal after the passed
* <code>JournalNode</code>.
* @param node Start the iteration with the first
* <code>JournalNode</code> after node
* that is for a <code>EntryTransition</code>.
*
* @throws NullPointerException if node is <code>null</code>.
*/
private TransitionIterator(JournalNode node) {
if (node == null)
throw new NullPointerException("node must be non-null");
current = node;
}
/**
* Return the next <code>EntryTransition</code> in the
* sequence, or <code>null</code> if the end
* of the sequence has been reached.
* @return The next <code>EntryTransition</code> in the
* sequence, or <code>null</code> if the
* end of the sequence has been reached.
* @throws IllegalStateException if
* <code>watcherRegistered</code> has not yet
* been called.
*/
EntryTransition next() {
if (end == null)
throw new IllegalStateException(
"watcherRegistered() not yet called");
/* We don't normally go off the end, but
* we set current equal to null when we are done
*/
if (current == null)
return null;
/* Note, the logic in here is a bit tricky since
* current is pre-incremented.
*/
// Skip if payload is not an EntryTransition
Object payload = current.payload;
while (true) {
if (current == end) {
/* This is the last one...still need to return
* it's payload if applicable.
*/
current = null;
if (payload instanceof EntryTransition)
// Might be null, but that's ok
return (EntryTransition)payload;
return null;
}
current = current.getNext();
assert current != null : "Iteration when off end";
if ((payload != null) &&
(payload instanceof EntryTransition))
{
return (EntryTransition)payload;
}
payload = current.payload;
}
}
/**
* Set the end of the iteration to ensure that
* any <code>EntryTransition</code> added after
* this iterator was created will either be returned by
* this iterator, or passed to the process() method
* of any watcher that was added to the watcher associated
* with the <code>OperationJournal</code> before
* this method was called.
* @throws IllegalStateException if
* <code>watcherRegistered</code> has been called.
*/
void watcherRegistered() {
if (end != null)
throw new IllegalStateException(
"watcherRegistered() called more than once");
end = lastProcessed(current);
if (current == end) {
/* Noting has been processed since we were created.
* There are no elements in the iteration.
*/
current = null;
return;
}
/* Skip the tail when we were created, we don't
* need to return it since it was in the journal before
* we were created.
*/
current = current.getNext();
}
/**
* Return the ordinal of the last operation posted
* when this iterator was created. All the
* <code>EntryTransition</code>s yielded by this
* iterator will have higher ordinals.
* @return the current ordinal when this
* iterator was created.
* @throws IllegalStateException if
* <code>watcherRegistered</code> has been called.
*/
long currentOrdinalAtCreation() {
if (end != null)
throw new IllegalStateException(
"watcherRegistered() has been called");
return current.ordinal;
}
}
/**
* Create a new <code>OperationJournal</code>.
* @param watchers Set of watchers that need
* to be asked if they are interested in the transitions.
* @throws NullPointerException if watchers is <code>null</code>.
*/
OperationJournal(TransitionWatchers watchers) {
super("OperationJournal");
if (watchers == null)
throw new NullPointerException("watchers must be non-null");
this.watchers = watchers;
synchronized (this) {
tail = new JournalNode(null);
}
lastProcessed = tail;
}
/**
* Records an operation on an entry. This method should be called
* <em>after</em> the transition has been made visible in
* <code>contents</code> (including any subsidiary
* objects such as the appropriate <code>EntryHandle</code>).
* @param transition an object describing the visibility
* transition of an entry.
* @throws NullPointerException if <code>transition</code> is
* <code>null</code>.
*/
synchronized void recordTransition(EntryTransition transition) {
if (transition == null)
throw new NullPointerException("transition must be non-null");
post(new JournalNode(transition));
}
/**
* Schedules a job that will call the <code>caughtUp</code> method
* on the passed <code>IfExistsWatcher</code> after the last
* posted <code>EntryTransition</code> is processed.
* @param watcher The watcher to notify when it is caught up.
* @throws NullPointerException if <code>watcher</code> is
* <code>null</code>.
*/
synchronized void markCaughtUp(IfExistsWatcher watcher) {
post(new JournalNode(new CaughtUpMarker(watcher)));
}
/**
* Return the ordinal of the last operation posted.
* Any operations with a higher ordinal should be considered
* to have taken place after this point in time.
* @return the ordinal of the last operation posted.
*/
synchronized long currentOrdinal() {
return lastOrdinalUsed;
}
/**
* Post a <code>JournalNode</code>
* @param node The node to post.
*/
private void post(JournalNode node) {
tail.next = node;
tail = node;
notifyAll();
}
/**
* Return an iterator that can latter be used to
* get all the <code>EntryTransition</code>s added after
* this point that have been processed.
* <p>
* We synchronize to make sure the initial state
* of the returned transition has been fetched from main memory.
* @return An iterator that can latter be used to
* get all the <code>EntryTransition</code>s added after
* this point that have been processed.
*/
synchronized TransitionIterator newTransitionIterator() {
return new TransitionIterator(tail);
}
/**
* Return the node currently being processed, or
* if no entry is currently being processed the
* last one that was processed. A watcher
* that was added to the <code>TransitionWatchers</code> object
* associated with this object before this call was made is
* guaranteed to be asked by the journal about any transition posted
* after the node returned by this method was posted.
* @param noEarlierThan The returned <code>JournalNode</code>
* is guaranteed to have not been posted before
* <code>noEarlierThan</code>. If the last
* node processes was posted before
* <code>noEarlierThan</code>, then
* <code>noEarlierThan</code> will be returned.
* @return The last node that has been at least
* partially processed.
*/
private synchronized JournalNode lastProcessed(
JournalNode noEarlierThan)
{
if (lastProcessed.ordinal < noEarlierThan.ordinal) {
return noEarlierThan;
}
return lastProcessed;
}
/**
* Terminate queue processing.
*/
synchronized void terminate() {
dead = true;
notifyAll();
}
/**
* Loop pulling transitions off the queue and
* process them by getting the set of interested watchers
* from the <code>TransitionWatchers</code> object associated
* with this object and then calling process on each
* of the watchers.
*/
public void run() {
while (!dead) {
try {
// Wait until there is something to process
synchronized (this) {
JournalNode n = lastProcessed.getNext();
while (n == null && !dead) {
wait();
n = lastProcessed.getNext();
}
if (dead)
return;
lastProcessed = n;
}
// Process based on payload
final Object payload = lastProcessed.payload;
if (payload == null) {
throw new
AssertionError("JournalNode with null payload");
} else if (payload instanceof EntryTransition) {
final EntryTransition t = (EntryTransition)payload;
final SortedSet set =
watchers.allMatches(t, lastProcessed.ordinal);
final long now = System.currentTimeMillis();
for (Iterator i=set.iterator(); i.hasNext() && !dead; ) {
final TransitionWatcher watcher =
(TransitionWatcher)i.next();
watcher.process(t, now);
}
} else if (payload instanceof CaughtUpMarker) {
((CaughtUpMarker)payload).watcher.caughtUp();
} else {
throw new
AssertionError("JournalNode with unknown payload:" +
payload.getClass());
}
} catch (InterruptedException e) {
// fin
return;
} catch (Throwable t) {
logger.log(Level.INFO, "OperationJournal.run encountered " +
t.getClass().getName() + ", continuing", t);
}
}
}
}