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 java.util.HashSet;
21  import java.util.Iterator;
22  import java.util.logging.Level;
23  import java.util.logging.Logger;
24  
25  import net.jini.core.transaction.server.TransactionConstants;
26  import net.jini.space.InternalSpaceException;
27  
28  /**
29   * Class that manages transaction related state on behalf of
30   * <code>EntryHandle</code>s. Can accommodate entries locked by
31   * more than one transaction.  The synchronization of this object is
32   * managed by the <code>EntryHandle</code> that owns it.
33   *
34   * @author Sun Microsystems, Inc.
35   *
36   * @see EntryHandle
37   */
38  class TxnState {
39      /**
40       * The list of known managers.  In order to keep things small in the
41       * common case that there is only one known manager, <code>mgrs</code>
42       * is managed as a ``list'' in one of two states -- it is either a
43       * direct reference to the only manager for this handle, or a reference
44       * to an <code>HashSet</code> with entries for each associated manager.
45       */
46      private Object mgrs;
47  
48      /**
49       * The current state of the handle, such as <code>READ</code> or
50       * <code>TAKE</code>.
51       */
52      private int state;
53  
54      /**
55       * The holder the handle which owns this object is in
56       */
57      final private EntryHolder holder;
58  
59      /** Logger for logging information about entry matching */
60      private static final Logger matchingLogger = 
61  	Logger.getLogger(OutriggerServerImpl.matchingLoggerName);
62  
63      /** Logger for logging transaction related information */
64      private static final Logger txnLogger = 
65  	Logger.getLogger(OutriggerServerImpl.txnLoggerName);
66  
67      /**
68       * Create a new <code>TxnState</code>.  It will start initially
69       * with the type of lock indicated by <code>state</code> under the
70       * transaction managed by <code>mgr</code>.  <code>holder</code> is
71       * the holder the associated entry handle is in.
72       */
73      TxnState(TransactableMgr mgr, int state, EntryHolder holder) {
74  	this.mgrs = mgr;
75  	this.state = state;
76  	this.holder = holder;
77  	txnLogger.log(Level.FINER, "TxnState: TxnState: state = {0}",
78  		      TransactableMgr.stateNames[state]);
79      }
80  
81      /**
82       * Prepare to commit this object's part of the transaction.  Return
83       * the prepare's status.
84       */
85      int prepare(TransactableMgr mgr, OutriggerServerImpl space,
86  		EntryHandle owner) 
87      {
88  	txnLogger.log(Level.FINEST, "TxnState: prepare: state = {0}",
89  		      TransactableMgr.stateNames[state]);
90  
91  	if (state == TransactableMgr.READ) {
92  	    final int locksLeft = removeMgr(mgr);
93  	    /* If there is more than one lock left resolving this lock
94  	     * does not change anything (takes still can't take the
95  	     * entry and reads could already) Also need to make sure
96  	     * it has not be removed (say by expiration or a cancel)
97  	     * since this would cause to send events for entries that
98  	     * were removed before it became visible. Also could cause
99  	     * ifExists queries to hang on to the entry and block
100 	     * indefinitely.  If they had already send the EntryTransition
101 	     * for the removal.
102 	     */
103 	    if (locksLeft <= 1 && !owner.removed()) {
104 		// Has the potential to resolve conflicts/generate events
105 		if (locksLeft == 1) {
106 		    space.recordTransition(
107  		         new EntryTransition(owner, mgr(), true, false, false));
108 		} else if (locksLeft == 0) {
109 		    space.recordTransition(
110  		         new EntryTransition(owner, null, true, false, false));
111 		} else {
112 		    throw new AssertionError("Fewer than 0 locks left");
113 		}
114 	    }
115 	    return TransactionConstants.NOTCHANGED;
116 	}
117 
118 	return TransactionConstants.PREPARED;
119     }
120 
121     /**
122      * Abort this object's part of the transaction.  Return true
123      * if this clears the last transaction associated with this object.
124      */
125     boolean abort(TransactableMgr mgr, OutriggerServerImpl space, 
126 		  EntryHandle owner) 
127     {
128 	boolean rslt = true;
129 
130 	txnLogger.log(Level.FINEST, "TxnState: abort: state = {0}",
131 		      TransactableMgr.stateNames[state]);
132 
133 	if (state == TransactableMgr.READ || state == TransactableMgr.TAKE) {
134 	    final int locksLeft = removeMgr(mgr);
135 	    rslt = (locksLeft == 0);
136 	    /* If there is more than one lock left resolving this
137 	     * lock does not change anything (takes still can't
138 	     * take the entry and reads could already). Also
139 	     * need to make sure it has not be removed (say
140 	     * by expiration or a cancel), otherwise
141 	     * we could log a transition to visible after
142 	     * the removal was logged, this could cause
143 	     * ifExists queries to hang on to the entry
144 	     * and block indefinitely. (This raises an
145 	     * issue if remote events were sent for re-appearance)
146 	     */
147 	    if (locksLeft <= 1 && !owner.removed()) {
148 		// Has the potential to resolve conflicts/generate events
149 		if (locksLeft == 1) {
150 		    /* There could have only been multiple locks if
151 		     * they were all read locks, thus the lock the
152 		     * abort resolves must h be a read lock, and the
153 		     * remaining lock must be a read lock too.
154 		     * Therefore this must be an availability but not a
155 		     * visibility transition.
156 		     */
157 		    assert state == TransactableMgr.READ;
158 
159 		    space.recordTransition(
160  		        new EntryTransition(owner, mgr(), true, false, false));
161 		} else if (locksLeft == 0) {
162 		    // Only a visibility transition if the lock being
163 		    // dropped was a take lock
164 		    final boolean visibility = (state == TransactableMgr.TAKE);
165 
166 		    space.recordTransition(
167 			new EntryTransition(owner, null, true, visibility,
168 					    false));
169 		} else {
170 		    throw new AssertionError("Fewer than 0 locks left");
171 		}
172 	    }
173 	} else {
174 	    /* must be a write, make the entry disappear, will
175 	     * call recordTransition()
176 	     */
177 	    holder.remove(owner, false);	    
178 	}
179 	
180 	return rslt;
181     }
182 
183     /**
184      * Commit this object's part of the transaction.  The
185      * <code>space</code> is the <code>OutriggerServerImpl</code> on
186      * which the operation happens -- some commit operations have
187      * space-wide side effects (for example, a commit of a
188      * <code>write</code> operation can cause event notifications for
189      * clients registered under the transaction's parent). Return true
190      * if this clears the last transaction associated with this
191      * object.
192      */
193     boolean commit(TransactableMgr mgr, OutriggerServerImpl space, 
194 		   EntryHandle owner) 
195     {
196 	txnLogger.log(Level.FINEST, "TxnState: commit: state = {0}",
197 		      TransactableMgr.stateNames[state]);
198 
199 	switch (state) {
200 	  case TransactableMgr.WRITE:
201 	      //!! assumption -- single level transactions:
202 	      //!! we pass null where we should pass the parent txn
203 	      if (owner.removed())
204 		  // if removed() then this entry must
205 		  // have been taken as well as written under this
206 		  // transaction.
207 		  return true;
208 
209 	      space.recordTransition(
210 		  new EntryTransition(owner, null, true, true, true));
211        	      return (removeMgr(mgr) == 0);
212 
213   	  case TransactableMgr.READ:
214 	      // Read locked entries should never get prepared.
215 	      throw new InternalSpaceException
216 	          ("committing a read locked entry");
217 
218 	  case TransactableMgr.TAKE:
219 	      // remove calls recordTransition()
220 	      holder.remove(owner, false);
221 	      // Resolves a lock
222 	      return true; // Take-locked entries only have one Txn
223 
224 	  default:
225 	      throw new InternalSpaceException("unexpected state in "
226 		  + "TxnState.commit(): " + state);
227 	}
228     }
229 
230     /**
231      * Remove the given mgr from the list of known managers.  Return the
232      * number of mgrs still associated with this entry.
233      */
234     private int removeMgr(TransactableMgr mgr) {
235 	if (mgrs == null)
236 	    return 0;
237 
238 	if (mgr == mgrs) {
239 	    mgrs = null;
240 	    return 0;
241 	}
242 
243 	final HashSet tab = (HashSet) mgrs;
244 	tab.remove(mgr);
245 	return tab.size();
246     }
247 
248 
249     /**
250      * Add <code>mgr</code> to the list of known managers, setting the
251      * state of this handle to <code>op</code>.
252      */
253     void add(TransactableMgr mgr, int op) {
254 	if (mgr == mgrs)
255 	    return;	// already the only one known
256 
257 	if (mgrs instanceof TransactableMgr) {
258 	    Object origMgr = mgrs;
259 	    mgrs = new HashSet(7);
260 	    ((HashSet) mgrs).add(origMgr);
261 	}
262 
263 	// if mgr is in already this is harmless, and checking to prevent
264 	// a redundant addition is more expensive
265 	((HashSet) mgrs).add(mgr);
266 	monitor(mgr);
267 	state = op;
268     }
269 
270     /**
271      * If we need to, add this manager to the list of transactions that
272      * need to be monitored because of conflicts over this entry.  Any
273      * existing blocking txn is sufficient.
274      *
275      * @see TxnMonitorTask#addSibling
276      */
277     private void monitor(TransactableMgr mgr) {
278 	Txn txn = (Txn) mgr;
279 	Iterator it = ((HashSet) mgrs).iterator();
280 	while (it.hasNext()) {
281 	    Txn otherTxn = (Txn) it.next();
282 	    if (otherTxn != mgr && otherTxn.monitorTask() != null) {
283 		otherTxn.monitorTask().addSibling(txn);
284 		return;
285 	    }
286 	}
287     }
288 
289     /**
290      * It this entry is read locked promote to take locked and return
291      * true, otherwise return false.  Assumes the take is being
292      * performed under the one transaction that owns a lock on the
293      * entry.  
294      */
295     boolean promoteToTakeIfNeeded() {
296 	// We can assume our state is ether WRITE or READ, if it was
297 	// take this method would not be called (since take blocks
298 	// other takes in the same transaction canPerform() would have
299 	// returned false)
300 
301 	if (state == TransactableMgr.WRITE)
302 	    return false;
303 
304 	state = TransactableMgr.TAKE;
305 	return true;
306     }
307 
308     /**
309      * Returns <code>true</code> if the operation <code>op</code>
310      * under the transaction manager by <code>mgr</code> is legal on
311      * the associated entry given the operations already performed on
312      * the entry under other transactions.  It is legal to:
313      * <ul>
314      * <li>Read an entry that has been read in any other transaction,
315      *     or that has been written under the same transaction (that is,
316      *     if <code>mgr</code> is the same as the writing transaction).
317      * <li>Take an entry that was written under the same transaction, or
318      *     which was read <em>only</em> under the same transaction (that is,
319      *     no other active transactions have also read it).
320      * </ul>
321      * All other operations are not legal, so <code>canPerform</code>
322      * otherwise returns <code>false</code>.  
323      */
324     boolean canPerform(TransactableMgr mgr, int op) {
325 	//Note: If two transactions are performed
326 	//	within the same TransactableMgr
327 	//	(see Txn.java), that means its in
328 	//	the same Transaction (see Transaction.java).
329 	//      Operations under the same Transaction scope
330 	//	are the same as if under a null Transaction
331 	//	i.e. all operations are visible.
332 	//
333 	//	Semantics need to be added for multi-level
334 	//	transactions.
335 
336 	if (matchingLogger.isLoggable(Level.FINER)) {
337 	    matchingLogger.log(Level.FINER,
338                 "TxnState: canPerform({0}, {1}): state = {2}",
339 		new Object[]{mgr, Integer.valueOf(op), 
340 			     TransactableMgr.stateNames[state]});
341 	}
342 
343 	switch (state) {
344 	  case TransactableMgr.READ:
345 	    if (op == TransactableMgr.READ)
346 		return true;
347 	    return onlyMgr(mgr); // can only modify if I'm the only participant
348 	  case TransactableMgr.WRITE:
349 	    if ( (op == TransactableMgr.READ) ||
350 		 (op == TransactableMgr.TAKE) )
351 		return onlyMgr(mgr);
352 	    return false;	  // can't read unless I'm the writer
353 	  case TransactableMgr.TAKE:
354 	    return false;	  // can't take anything taken any time
355 	}
356 	return false;
357     }
358 
359     /**
360      * Return <code>true</code> if <code>mgr</code> is one of the managers
361      * known to be managing this entry.
362      */
363     boolean knownMgr(TransactableMgr mgr) {
364 	if (mgr == null)
365 	    return false;
366 	if (mgr == mgrs)
367 	    return true;
368 	if (mgrs instanceof HashSet)
369 	    return ((HashSet) mgrs).contains(mgr);
370 	return false;
371     }
372 
373     /**
374      * Return <code>true</code> if the given manager is the only one
375      * we know about.
376      */
377     boolean onlyMgr(TransactableMgr mgr) {
378 	if (mgr == null)
379 	    return false;
380 	if (mgr == mgrs)
381 	    return true;
382 	if (mgrs instanceof HashSet) {
383 	    HashSet tab = (HashSet) mgrs;
384 	    return (tab.size() == 1 && tab.contains(mgr));
385 	}
386 	return false;
387     }
388 
389     /**
390      * Add all the managers of this transaction to the given
391      * collection.
392      */
393     void addTxns(java.util.Collection collection) {
394 	if (mgrs == null)
395 	    return;
396 
397 	if (mgrs instanceof HashSet) {
398 	    final HashSet tab = (HashSet)mgrs;
399 	    collection.addAll((HashSet) mgrs);
400 	    return;
401 	}
402 
403 	collection.add(mgrs);
404     }
405 
406     /**
407      * Return true if there are no more transactions associated with
408      * this object.
409      */
410     boolean empty() {
411 	if (mgrs == null)
412 	    return true;
413 
414 	if (mgrs instanceof HashSet) 
415 	    return ((HashSet)mgrs).isEmpty();
416 	
417 	return false;
418     }
419 
420     /** Used by mgr() */
421     final private TransactableMgr[] rslt = new TransactableMgr[1];
422 
423     /**
424      * Returns the one manager associated with this transaction.
425      * Throws an AssertionError if there is more or fewer than one
426      * manager associated with this transaction.
427      */
428     private TransactableMgr mgr() {	
429 	if (mgrs == null)
430 	    throw new AssertionError
431 		("mgr() called on a TxnState with no manager");
432 
433 	if (mgrs instanceof HashSet) { 
434 	    final HashSet tab = (HashSet)mgrs;
435 	    if (tab.size() != 1)
436 		throw new AssertionError
437 		    ("mgr() called on TxnState with more than one manager");
438 	    tab.toArray(rslt);
439 	    return rslt[0];
440 	}
441 
442 	return (TransactableMgr)mgrs;
443     }
444 }