/*
* 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.Set;
/**
* Subclass of <code>QueryWatcher</code> for non-transactional if
* exists reads. Resolves with the first matching
* transition where the transaction is <code>null</code> and the entry
* is visible (the entry's current state is ignored) or if
* the locked entry set goes empty.
*/
class ReadIfExistsWatcher extends SingletonQueryWatcher
implements IfExistsWatcher
{
/**
* The set of entries that would match but are currently
* unavailable (e.g. they are locked). We only keep
* the ids, not the entries themselves.
*/
private final Set lockedEntries;
/**
* Set <code>true</code> once the query thread is
* done processing the backlog. Once this is
* <code>true</code> it is ok to resolve if
* <code>lockedEntries</code> is empty.
*/
private boolean backlogFinished = false;
/**
* Create a new <code>ReadIfExistsWatcher</code>.
* @param expiration the initial expiration time
* for this <code>TransitionWatcher</code> in
* milliseconds since the beginning of the epoch.
* @param timestamp the value that is used
* to sort <code>TransitionWatcher</code>s.
* @param startOrdinal the highest ordinal associated
* with operations that are considered to have occurred
* before the operation associated with this watcher.
* @param lockedEntries Set of entries (by their IDs)
* that match but are unavailable. Must be non-empty.
* Keeps a reference to this object.
* @throws NullPointerException if <code>lockedEntries</code> is
* <code>null</code>.
*/
ReadIfExistsWatcher(long expiration, long timestamp, long startOrdinal,
Set lockedEntries)
{
super(expiration, timestamp, startOrdinal);
if (lockedEntries == null)
throw new NullPointerException("lockedEntries must be non-null");
this.lockedEntries = lockedEntries;
}
boolean isInterested(EntryTransition transition, long ordinal) {
/* If we are unresolved pretty much all transitions are
* interesting because we may need to update
* lockedEntries. The only exception is read locks being
* resolved. It is important that transitions triggered by the
* release of read locks get filtered out - otherwise process
* could end up adding and removing elements to lockedEntries
* when it shouldn't.
*
* Note, !isResolved() without the lock will result only in
* false positives, not false negatives - it will only
* cause isInterested() to return false if we are resolved,
* we may still return true if we are resolved though.
*/
if (!transition.isVisible() && transition.isAvailable()) {
/* must be a transition triggered by a read lock release,
* wont change anything so ignore it.
*/
return false;
}
return (ordinal>startOrdinal) && !isResolved();
}
synchronized void process(EntryTransition transition, long now) {
if (isResolved())
return; // Already done.
final EntryRep rep = transition.getHandle().rep();
final boolean isVisible = transition.isVisible();
/* If the entry was visible at one time to the null
* transaction we can just resolve.
*/
if (isVisible && (transition.getTxn() == null)) {
resolve(transition.getHandle(), null);
} else if (isVisible) { // && getTxn() != null
/* If we are here transition.getTxn() must be != null
* and the entry was visible, implying that it was an entry
* written under a transaction and is interesting to
* us, but not yet visible. We need to add it lockedEntries
* even if has been removed since it could have gotten
* replaced before it was removed. If we did not
* add it we would be acting on the future.
*/
lockedEntries.add(rep.id());
} else {
/* Must not be visible (and becaues of the test in
* isInteresting can't be available either - that is
* transition can't just be the release of a read lock),
* transition must mark the resolution of the transaction
* in such away that the entry has been removed, remove it
* from the set and see if that makes the set empty.
*/
lockedEntries.remove(rep.id());
if (backlogFinished && lockedEntries.isEmpty())
resolve(null, null);
}
}
synchronized boolean catchUp(EntryTransition transition, long now) {
if (isResolved())
return true; // Already done.
final EntryHandle handle = transition.getHandle();
final EntryRep rep = handle.rep();
final boolean isVisible = transition.isVisible();
/* Was this the resolution of a read lock? if so ignore */
if (!isVisible && transition.isAvailable())
return false;
/* If the entry was visible at one time to the null
* transaction we can just resolve.
*/
if (isVisible && (transition.getTxn() == null)) {
resolve(handle, null);
return true;
}
if (isVisible) { // && getTxn() != null
/* If we are here transition.getTxn() must be != null and
* the entry is/was visible to someone, implying that it
* was an entry written under a transaction and is
* interesting to us, but not yet visible to us. We only
* add if it has not already been removed. It might have
* gotten replaced before removal, but since we won't let
* this query get resolved with a definitive null before
* we get past the point in the journal where it was
* removed it is ok to never put it in (and if we did put
* it in it might never get removed since process() may
* have already processed the removal). We don't need to
* check to see it the entry has been provisionally
* removed since if has been provisional removal does not
* put in entries in the journal and if it is
* provisionally removed it has not yet been removed so
* the remove recored has not yet been created (must less
* processed).
*/
synchronized (handle) {
if (!handle.removed()) {
lockedEntries.add(rep.id());
}
}
// Either way, still not resolved.
return false;
}
/* Must not be visible (and because of the first test can't be
* available either - that is transition can't just be the
* release of a read lock), transition must mark the
* resolution of the transaction in such away that the entry
* has been removed, remove it from the set (don't need to
* check for empty because we haven't gotten to the point
* where we can resolve with a definitive null.)
*/
lockedEntries.remove(rep.id());
return false;
}
/**
* Once the backlog is complete we can resolve if
* lockedEntries is/becomes empty.
*/
public synchronized void caughtUp() {
backlogFinished = true;
if (isResolved())
return; // Don't much mater.
if (lockedEntries.isEmpty())
resolve(null, null);
}
public synchronized boolean isLockedEntrySetEmpty() {
if (!isResolved())
throw new IllegalStateException("Query not yet resolved");
return lockedEntries.isEmpty();
}
}