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 }