View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.river.outrigger;
19  
20  import net.jini.io.MarshalledInstance;
21  import org.apache.river.landlord.LeasedResource;
22  import org.apache.river.outrigger.proxy.EntryRep;
23  import java.util.Queue;
24  
25  /**
26   * This object holds an annotated reference to an
27   * <code>EntryRep</code> object.  Currently there is one annotation,
28   * which is a hash code for the object that can be used as a
29   * quick-reject comparison when scanning through the list.  The handle
30   * holds a hash code that is based on the bytes that encode the first
31   * <i>N</i> fields, where <i>N</i> is the number of fields in the
32   * entry up to a maximum (currently this maximum is 16 (64 bits
33   * divided by 4 bits/field), so that 4 is the minimum number of bits
34   * per field in the hash).
35   * <p>
36   * When comparing, the template's own hash is calculated, and also a
37   * mask that masks out the hash codes of wildcard fields.  A template
38   * will match an entry only if the entry's EntryHandle hash masked with
39   * the template's wildcard mask is the same as the template's hash.
40   * <p>
41   * Care must be taken since the template may be a supertype of the type
42   * being searched.  This is why the number of fields in the static
43   * methods is passed as an argument, not simply taken from the entry in
44   * question.  When a template's hash is being created, its hash value
45   * is calculated as if it were of the class being searched, with the
46   * subclass's field count.  Any extra fields are assumed to be
47   * wildcards.  This means that the template's hash must be recalculated
48   * for each subclass it is compared against, but this only happens once
49   * per known subclass, and so is probably not onerous.  <p>
50   *
51   * There is a particular risk with the removal of entries outside of
52   * transactions. Ideally marking an entry as removed and making the
53   * removal durable would be atomic with respect to other
54   * operations. But this would require holding a lock across disk I/O
55   * which we try to avoid. In particular it would hold up the progress
56   * of searches that match the entry in question, even though the very
57   * next entry might be a suitable match. One alternative would be to
58   * make the removal durable, then while holding the entry's lock mark
59   * the entry as removed, but this would allow competing takes to both
60   * get the same entry (this could be corrected by making the 2nd take
61   * lose when it goes back to try and complete the removal, and then
62   * continues its query, but since logging happens up in
63   * OutriggerServerImpl restarting the query would be inconvenient, it
64   * would probably also result in a number of unnecessary log
65   * records). We could mark the entry as removed, release the entry's
66   * lock, and then make the removal durable. However, this allows for
67   * the possibility of a 2nd query that matches the entry coming in
68   * after the entry has been removed, but before the removal has been
69   * made durable, finding no matches and returning null, and then the
70   * server crashing before the removal is made durable. When the server
71   * came back up the entry would be available again, and if the 2nd
72   * query was repeated it could then return the entry that had been
73   * marked as removed. Effectively an entry would have disappeared and
74   * then reappeared. <p>
75   * 
76   * Our solution is to introduce the <i>removePending</i> flag. When an
77   * entry is to be removed outside of a transaction the removePending
78   * flag is set by calling <code>provisionallyRemove</code>, the
79   * removal is made durable, the entry is removed internally and the
80   * removePending flag cleared (generally by calling
81   * <code>remove</code> on the appropriate <code>EntryHolder</code> or
82   * on the <code>EntryHolderSet</code> - either will remove the entry
83   * from all the internal tables and clear the removePending flag). <p>
84   *
85   * Any operation that will definitively indicate that a given entry
86   * has been removed must not only check to see if the entry has been
87   * removed but also that removePending is not set (the
88   * <code>isProvisionallyRemoved</code> method returns the state of the
89   * removePending flag). If removePending is set the operation must
90   * either block until removePending is cleared (this can be
91   * accomplished using the <code>waitOnCompleteRemoval</code> method),
92   * indicating that the removal has been made durable, or return in
93   * such a way that the entry's state is left ambiguous. Note, because
94   * any I/O failure while logging will result in the space crashing a
95   * set removePending flag will only transition to cleared after a
96   * removal has been made durable, thus an operation blocked on the
97   * removePending flag should never need to go back and see if the
98   * entry has become available. <p>
99   *
100  * Note some of the method of this class are synchronized internally,
101  * while other are synchronized externally.  Methods which need to be
102  * synchronized externally are called out in their comments.
103  *
104  * @author Sun Microsystems, Inc.  
105  */
106 // We do not store this data on the EntryRep object itself because it
107 // is not really part of the client<->JavaSpaces service protocol -- 
108 // some implementations of EntryHolder may not choose to use this
109 // mechanism.  It does add an extra object per EntryRep object in
110 // those that *do* use it, and so we may want to re-examine this in the
111 // future.
112 
113 class EntryHandle extends BaseHandle implements LeaseDesc, Transactable {
114     /** the content hash for the rep */
115     private final long     hash; // Made final for toString() and hash().
116 
117     /** 
118      * If this entry is locked by one or more transaction the info
119      * on those transactions, otherwise <code>null</code>.
120      */
121     private TxnState txnState;
122 
123     /**
124      * <code>true</code> if this entry has to been seen as removed,
125      * but the removal has not yet been committed to disk
126      */
127     private boolean removePending = false;
128     private boolean removed = false;
129 
130     /**
131      * Create a new handle, calculating the hash for the object.
132      * If <code>mgr</code> is non-<code>null</code> start the entry
133      * as write locked under the given transaction.
134      * @param rep The rep of the entry this is a handle for
135      * @param mgr If this entry is being written under a transaction the
136      *            manager for that transaction, otherwise <code>null</code>
137      * @param holder If mgr is non-<code>null</code> this must be
138      *            the holder holding this handle.  Otherwise it may be
139      *            <code>null</code> 
140      * @param content Queue this EntryHandle will be removed from.
141      */
142     EntryHandle(EntryRep rep, TransactableMgr mgr, EntryHolder holder, Queue<EntryHandle> content) {
143 	super(rep, content);
144 	hash = (rep != null ? hashFor(rep, rep.numFields())[0] : -1);
145 	if (mgr == null) {
146 	    txnState = null;
147 	} else {
148 	    if (holder == null) 
149 		throw new NullPointerException("EntryHandle:If mgr is " +
150  	            "non-null holder must be non-null");
151 	    txnState = new TxnState(mgr, TransactableMgr.WRITE, holder);
152 	}
153     }
154 
155     // inherit doc comment
156     public LeasedResource getLeasedResource() {
157 	return rep();
158     }
159 
160     /**
161      * Return this handle's content hash.
162      */
163     long hash() {
164 	return hash;
165     }
166 
167     /**
168      * Calculate the hash for a particular entry, assuming the given
169      * number of fields, filling in the fields of <code>desc</code>
170      * with the relevant values.  <code>desc</code> may be
171      * <code>null</code>.  <code>numFields</code> must be >= the number
172      * of fields in the <code>rep</code> object (this is not
173      * checked).
174      *
175      * @see #hashFor(EntryRep,int)
176      * @see #descFor(EntryRep,int)
177      * @return long[4] containing the hash, bitsPerField, fieldsInHash and mask
178      * in that order.
179      */
180     private static long []
181 	hashFor(EntryRep rep, int numFields)
182     {
183         long [] result = new long [4];
184 	if (rep == null || numFields == 0) return result;
185         
186         /** Number of bits allocated in the hash for each field */
187 	int bitsPerField = Math.max(64 / numFields, 4);	// at least 4 bits
188         /** How many fields are used in the hash? */
189 	int fieldsInHash = 64 / bitsPerField;		// max fields used
190         /** A mask with the lower <code>bitsPerField</code> bits set */
191 	long mask =					// per-field bit mask
192 		    0xffffffffffffffffL >>> (64 - bitsPerField);
193 	long hash = 0;					// current hash value
194 
195 	// field counts will be different if rep is a template of a superclass
196 	long endField = Math.min(fieldsInHash, rep.numFields());
197 
198 	// set the appropriate rep of the overall hash for the field's hash
199 	for (int i = 0; i < endField; i++)
200 	    hash |= (hashForField(rep, i) & mask) << (i * bitsPerField);
201         
202         result [0] = hash;
203         result [1] = bitsPerField;
204         result [2] = fieldsInHash;
205         result [3] = mask;
206 	return result;
207     }
208 
209     /**
210      * Return the template description -- mask and hash.
211      *
212      * @see EntryHandleTmplDesc
213      */
214     static EntryHandleTmplDesc descFor(EntryRep tmpl, int numFields) {
215 	EntryHandleTmplDesc tmplDesc;
216         
217 	// Get the hash and the related useful information
218         long [] hashDesc = hashFor(tmpl, numFields);
219 	long hash = hashDesc[0];
220         long bitsPerField = hashDesc [1];
221         long fieldsInHash = hashDesc [2];
222         long mask = hashDesc [3];
223         
224         long tmplMask = 0;
225 
226 	// Create the mask to mask away wildcard fields
227 	for (int i = 0; i < fieldsInHash; i++) {
228 	    // If this field is one we have a value for, set bits in the mask
229 	    if (i < tmpl.numFields() && tmpl.value(i) != null)
230 		tmplMask |= (mask << (i * bitsPerField));
231 	}
232         
233 	// Ensure that the non-value fields are masked out
234 	hash &= tmplMask;
235         
236         tmplDesc = new EntryHandleTmplDesc(hash, tmplMask);
237 
238 	return tmplDesc;
239     }
240 
241     /**
242      * Return the hash value for a given field, which is then merged in
243      * as part of the overall hash for the entry.  The last 32 bytes of
244      * the field value are used (or fewer if there are fewer).
245      *
246      * @see #hashFor(EntryRep,int)
247      */
248     static long hashForField(EntryRep rep, int field) {
249 	MarshalledInstance v = rep.value(field);
250 	if (v == null)	  // for templates, it's just zero
251 	    return 0;
252 	else
253 	    return v.hashCode();
254     }
255 
256     public String toString() {
257 	return "0x" + Long.toHexString(hash) + " [" + rep() + "]";
258     }
259 
260     /**
261      * Return <code>true</code> if the operation <code>op</code> under
262      * the given transaction (represented by the transaction's manager)
263      * can be performed on the object represented by this handle.  The 
264      * thread calling this method should own this object's lock.
265      */
266     // $$$ Calling this method when we don't own the lock on this
267     // object seems a bit dicey, but that is exactly what we do in 
268     // EntryHolder.SimpleRepEnum.nextRep().  Working it through
269     // it seems to work in that particular case, but it seems fragile.
270     boolean canPerform(TransactableMgr mgr, int op) {
271         synchronized (this){ // Audit revealed calling thread didn't always own lock see comment above.
272             if (txnState == null) 
273                 return true; // all operations are legal on a non-transacted entry
274 
275             return txnState.canPerform(mgr, op);
276         }
277     }
278 
279     /**
280      * Return <code>true</code> if the given transaction is already
281      * known to the entry this handle represents.  The 
282      * thread calling this method should own this object's lock.
283      */
284     boolean knownMgr(TransactableMgr mgr) {
285         synchronized (this){  // Audit revealed caller methods didn't always hold lock.
286             if (txnState == null) 
287                 return (mgr == null); // The only mgr we know about is the null mgr
288 
289             return txnState.knownMgr(mgr);
290         }
291     }
292 
293     /**
294      * Return <code>true</code> if we are being managed the given
295      * manager is the only one we know about.  The thread calling this
296      * method should own this object's lock.
297      */
298     boolean onlyMgr(TransactableMgr mgr) {
299 	if (txnState == null) // Audit revealed caller always held lock.
300 	    return false;
301 
302 	return txnState.onlyMgr(mgr);
303     }
304 
305     /**
306      * Return <code>true</code> if the entry this handle represents is
307      * being managed within any transaction.  The thread calling this
308      * method should own this object's lock.
309      */
310     boolean managed() {
311         synchronized (this){  // Audit revealed caller didn't always have lock.
312             return txnState != null;
313         }
314     }
315 
316     /**
317      * Add into the collection any transactions that are known to this
318      * handle. The thread calling this method should own this object's
319      * lock.
320      */
321     void addTxns(java.util.Collection collection) {
322 	if (txnState == null) // Confirmed that calling method owns lock.
323 	    return; // nothing to add
324 
325 	txnState.addTxns(collection);
326     }
327 
328     /**
329      * Add <code>mgr</code> to the list of known managers, setting the
330      * the type of lock on this entry to <code>op</code>. The thread
331      * calling this method should own this object's lock.  Assumes
332      * that <code>op</code> is compatible with any lock currently
333      * associated with this entry.  <code>holder</code> is the the
334      * <code>EntryHolder</code> holding this handle.
335      */
336     void add(TransactableMgr mgr, int op, EntryHolder holder) {
337         synchronized (this){
338             if (txnState == null) {
339                 txnState = new TxnState(mgr, op, holder);
340             } else {
341                 txnState.add(mgr, op);
342             }
343         }
344     }
345 
346     /**
347      * It this entry is read locked promote to take locked and return
348      * true, otherwise return false.  Assumes that the object is
349      * locked and the take is being performed under the one
350      * transaction that owns a lock on the entry.
351      */
352     boolean promoteToTakeIfNeeded() {
353         synchronized (this){
354             return txnState.promoteToTakeIfNeeded();
355         }
356     }
357 
358     /**
359      * Returns <code>true</code> it this entry has been removed
360      * outside of a transaction, but that removal has not yet been
361      * committed to disk.The thread calling this method should own this
362      * object's lock.
363      */
364     boolean isProvisionallyRemoved() {
365 	assert Thread.holdsLock(this);
366 	return removePending;
367     }
368 
369     /**
370      * Marks this entry as being removed outside of a transaction but
371      * not yet committed to disk. The thread calling this method should
372      * own this object's lock.
373      */
374     void provisionallyRemove() {
375 	assert Thread.holdsLock(this);
376 	assert !removePending;
377 	removePending = true;
378     }
379 
380     /**
381      * Called after the removal of a provisionally removed entry has
382      * been committed to disk and the handle has been removed from its
383      * holder. The thread calling this method should own this object's
384      * lock.
385      */
386     void removalComplete() {
387 	assert Thread.holdsLock(this);
388 
389 	if (removePending) {
390 	    removePending = false;
391 	    notifyAll();
392 	}
393     }
394 
395     /**
396      * If this entry has been marked for removal by a
397      * non-transactional operation, but that operation has not be
398      * yet been committed to disk, block until the operation has been
399      * committed to disk, otherwise return immediately. The
400      * thread calling this method should own this object's lock.
401      */
402     void waitOnCompleteRemoval() throws InterruptedException {
403 	assert Thread.holdsLock(this);
404 	while (removePending) {
405 	    wait();
406 	}
407     }
408 
409     /**************************************************
410      * Methods required by the Transactable interface 
411      **************************************************/
412     public synchronized int prepare(TransactableMgr mgr, 
413 				    OutriggerServerImpl space) 
414     {
415 	if (txnState == null)
416 	    throw new IllegalStateException("Can't prepare an entry not " +
417 					    "involved in a transaction");
418 	final int rslt = txnState.prepare(mgr, space, this);
419 	if (txnState.empty())
420 	    txnState = null;
421 
422 	return rslt;
423     }
424 
425     public synchronized void abort(TransactableMgr mgr, 
426 				   OutriggerServerImpl space) 
427     {
428 	if (txnState == null)
429 	    throw new IllegalStateException("Can't abort an entry not " +
430 					    "involved in a transaction");
431 	final boolean last = txnState.abort(mgr, space, this);
432 	if (last)
433 	    txnState = null;
434     }
435 
436     public synchronized void commit(TransactableMgr mgr, 
437 				    OutriggerServerImpl space) 
438     {
439 	if (txnState == null)
440 	    throw new IllegalStateException("Can't commit an entry not " +
441 					    "involved in a transaction");
442 
443 	final boolean last = txnState.commit(mgr, space, this);
444 	if (last)
445 	    txnState = null;
446     }
447 
448     @Override
449     public synchronized boolean removed() {
450         return removed;
451     }
452     
453     public synchronized boolean remove() {
454         if (!removed) removed = super.remove();
455         return removed;
456     }
457 }