View Javadoc

1   /*
2    * 
3    * Copyright 2005 Sun Microsystems, Inc.
4    * 
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    * 
9    *  http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   * 
17   * Note: Changes by the Jgroup team are marked ** GAHALO **.
18   * 
19   */
20  package com.sun.jini.mahalo;
21  
22  import java.io.Serializable;
23  import java.rmi.RemoteException;
24  import java.util.Date;
25  import java.util.Enumeration;
26  import java.util.List;
27  import java.util.Vector;
28  import java.util.logging.Level;
29  import java.util.logging.Logger;
30  
31  import jgroup.jini.txn.InternalPassiveGroupTransactionManager;
32  
33  import net.jini.core.transaction.CannotAbortException;
34  import net.jini.core.transaction.CannotCommitException;
35  import net.jini.core.transaction.CannotJoinException;
36  import net.jini.core.transaction.TimeoutExpiredException;
37  import net.jini.core.transaction.Transaction;
38  import net.jini.core.transaction.TransactionException;
39  import net.jini.core.transaction.UnknownTransactionException;
40  import net.jini.core.transaction.server.CrashCountException;
41  import net.jini.core.transaction.server.ServerTransaction;
42  import net.jini.core.transaction.server.TransactionConstants;
43  import net.jini.core.transaction.server.TransactionManager;
44  import net.jini.core.transaction.server.TransactionParticipant;
45  import net.jini.id.Uuid;
46  import net.jini.security.ProxyPreparer;
47  
48  import com.sun.jini.constants.TimeConstants;
49  import com.sun.jini.constants.TxnConstants;
50  import com.sun.jini.landlord.LeasedResource;
51  import com.sun.jini.logging.Levels;
52  import com.sun.jini.mahalo.log.ClientLog;
53  import com.sun.jini.mahalo.log.LogException;
54  import com.sun.jini.mahalo.log.LogManager;
55  import com.sun.jini.thread.TaskManager;
56  import com.sun.jini.thread.WakeupManager;
57  
58  /**
59   * TxnManagerTransaction is a class which
60   * captures the internal representation of a transaction
61   * in the TxnManagerImpl server.  This class is associated
62   * with a transaction id.
63   * The information encapsulated includes the list of participants
64   * which have joined the transaction, the state of the
65   * transaction, the crash.
66   *
67   * The user of a ParticipantHolder must make the association
68   * between an instance of a ParticipantHolder and some sort
69   * of key or index.
70   *
71   * @author Sun Microsystems, Inc.
72   *
73   * ** GAHALO ** Added Serializable support. 
74   */
75  public class GroupTxnManagerTransaction
76      implements TransactionConstants, TimeConstants, LeasedResource, Serializable
77  {
78  
79    ////////////////////////////////////////////////////////////////////////////////////////////
80    // ** GAHALO ** Obtains a log4j Logger for this class
81    ////////////////////////////////////////////////////////////////////////////////////////////
82  
83    private final static org.apache.log4j.Logger log = org.apache.log4j.Logger
84        .getLogger(GroupTxnManagerTransaction.class);
85  
86    private static final long serialVersionUID = -2088463193687796098L;
87  
88     /*
89      * Table of valid state transitions which a
90      * TransactionManager may make.
91      *
92      * This represents the following diagram from the
93      * Jini(TM) Transaction Spec:
94      *
95      *  ACTIVE ----> VOTING ------->COMMITTED
96      *     \           |
97      *      \          |
98      *       ---------------------->ABORTED
99      *
100     *
101     *           ACTIVE  VOTING  PREPARED  NOTCHANGED  COMMITTED  ABORTED
102     * ----------------------------------------------------------------------
103     * ACTIVE        true     true   false     false       false       true
104     * VOTING        false    true   false     false       true        true
105     * PREPARED      false    false  false     false       false       false
106     * NOTCHANGED    false    false  false     false       false       false
107     * COMMITTED     false    false  false     false       true        false
108     * ABORTED       false    false  false     false       false       true
109     *
110     * The table is indexed using the ordered pair
111     * <current_state, next_state>.  A value of true means
112     * that the transition is possible, while a false
113     * means that the transition is not possible.
114     *
115     * Note:  Some rows are "{false, false, false, false}" as
116     *        unused filler to account for the fact that the
117     *        TransactionManager's valid states are a subset
118     *        of the TransactionConstants.
119     *
120     *    <zero>
121     *    ACTIVE = 1
122     *    VOTING = 2
123     *    PREPARED = 3
124     *    NOTCHANGED = 4
125     *    COMMITTED = 5
126     *    ABORTED = 6
127     */
128     private static final boolean states[][] = {
129     /* <zero>    */ {false, false, false, false, false, false, false},
130     /* ACTIVE    */ {false, true,  true,  false, false, false, true},
131     /* VOTING    */ {false, false, true,  false, false, true,  true},
132     /* PREPARED  */ {false, false, false, false, false, false, false},
133     /* NOTCHANGED*/ {false, false, false, false, false, false, false},
134     /* COMMITTED */ {false, false, false, false, false, true,  false},
135     /* ABORTED   */ {false, false, false, false, false, false, true}};
136 
137     /**
138      * @serial
139      */
140     private List parts = new Vector();
141 
142     /**
143      * @serial
144      */
145     private final ServerTransaction str;
146 
147     /**
148      * @serial
149      */
150     private int trstate;
151 
152     /**
153      * @serial
154      */
155     private long expires;       //expiration time
156 
157     /**
158      * ** GAHALO ** Set field to transient.
159      */
160     private transient LogManager logmgr;
161 
162 
163    /**
164     * "Parallelizing" the interaction between the manager
165     * and participants means using threads to interact with
166     * participants on behalf of the manager. In the thread
167     * pool model, a TaskManager provides a finite set of
168     * threads used to accomplish a variety of tasks.
169     * A Job encapsulates a body of work which needs to be
170     * performed during the two-phase commit: preparing,
171     * committing and aborting.  Each work item is broken
172     * into smaller pieces of work- interactions with a
173     * single participant- each assigned to a task.
174     *
175     * When a transaction is committing, a PrepareJob is
176     * created and its tasks are scheduled.  After completion,
177     * the PrepareJob's outcome is computed. Depending on
178     * the outcome, either an AbortJob or CommitJob is
179     * scheduled.  When a transaction is aborted, an AbortJob
180     * is scheduled.
181     *
182     * A caller may specify a timeout value for commit/abort.
183     * The timeout represents the length of time a caller is
184     * willing to wait for participants to be instructed to
185     * roll-forward/back.  Should this timeout expire, a
186     * TimeoutExpiredException is thrown.  This causes the
187     * caller's thread to return back to the caller.  Someone
188     * needs to finish contacting all the participants.  This
189     * is accomplished by the SettlerTask.  SettlerTasks must
190     * use a different thread pool from what is used by the
191     * various Job types, otherwise deadlock will occur.
192     *
193     * ** GAHALO ** Set field to transient.
194     */
195     private transient TaskManager threadpool;
196 
197     /**
198      * ** GAHALO ** Set field to transient.
199      */
200     private transient WakeupManager wm;
201 
202     /**
203      * ** GAHALO ** Set field to transient.
204      */
205     private transient TxnSettler settler;
206 
207     /**
208      * ** GAHALO ** Set field to transient.
209      */
210     private transient Job job;
211 
212     /**
213      * @serial
214      */
215     private Uuid uuid;
216 
217    /**
218     * Interlock for the expiration time since
219     * lease renewal which set it may compete
220     * against lease checks which read it.
221     *
222     * ** GAHALO ** Set field to transient.
223     */
224     private transient Object leaseLock = new Object();
225 
226 
227    /**
228     * Interlock for Jobs is needed since many
229     * threads on behalf of many clients can
230     * simultaneously access or modify the Job
231     * associated with this transaction when
232     * when attempting to prepare, roll forward
233     * or roll back participants.
234     *
235     * ** GAHALO ** Set field to transient.
236     */
237     private transient Object jobLock = new Object();
238 
239 
240    /**
241     * Interlock for transaction state needed
242     * since many threads on behalf of many
243     * clients can simultaneously access or
244     * attempt to modify this transaction's
245     * state as a side effect of calling
246     * commit or abort.
247     *
248     * ** GAHALO ** Set field to transient.
249     */
250     private transient Object stateLock = new Object();
251 
252 
253     /** Logger for operation related messages */
254     private static final Logger operationsLogger = 
255         TxnManagerImpl.operationsLogger;
256 
257     /** Logger for transaction related messages */
258     private static final Logger transactionsLogger = 
259         TxnManagerImpl.transactionsLogger;
260 
261     /** 
262      * ** GAHALO **
263      * New fields to support passive replication.
264      */    
265     /** The transaction ID */
266     private final long id;
267 
268     /** Are the backups notified of commit */
269     private boolean backupsCommitted = false;
270 
271     /** Start time of prepare */
272     private long startprepare;
273 
274     /** End time of prepare */
275     private long endprepare;
276 
277     /** Start time of commit */
278     private long startcommit;
279 
280     /** End time of commit */
281     private long endcommit;
282 
283     
284     /**
285      * Constructs a <code>TxnManagerTransaction</code>
286      *
287      * @param mgr   <code>TransactionManager</code> which owns
288      *          this internal representation.
289      * @param logmgr    <code>LogManager</code> responsible for
290      *          recording COMMITTED and ABORTED transactions
291      *          to stable storage.
292      *
293      * @param id    The transaction id
294      *
295      * @param threadpool The <code>TaskManager</code> which provides
296      *           the pool of threads used to interact with
297      *           participants.
298      *
299      * @param settler   TxnSettler responsible for this transaction if
300      *          unsettled.
301      */
302     GroupTxnManagerTransaction(TransactionManager mgr, LogManager logmgr, long id,
303         TaskManager threadpool, WakeupManager wm, TxnSettler settler,
304     Uuid uuid) 
305     {
306     if (logmgr == null)
307         throw new IllegalArgumentException("GroupTxnManagerTransaction: " +
308                 "log manager must be non-null");
309     if (mgr == null)
310         throw new IllegalArgumentException("GroupTxnManagerTransaction: " +
311                 "transaction manager must be non-null");
312 
313     if (threadpool == null)
314         throw new IllegalArgumentException("GroupTxnManagerTransaction: " +
315                 "threadpool must be non-null");
316 
317     if (wm == null)
318         throw new IllegalArgumentException("GroupTxnManagerTransaction: " +
319                 "wakeup manager must be non-null");
320 
321     if (settler == null)
322         throw new IllegalArgumentException("GroupTxnManagerTransaction: " +
323                 "settler must be non-null");
324 
325     if (uuid == null)
326         throw new IllegalArgumentException("GroupTxnManagerTransaction: " +
327                 "uuid must be non-null");
328 
329     this.threadpool = threadpool;
330     this.wm = wm;
331     this.logmgr = logmgr ;
332     str = new ServerTransaction(mgr, id);
333     this.settler = settler;
334     this.uuid = uuid;
335     
336     /** ** GAHALO ** */
337     this.id = id;
338     
339     trstate = ACTIVE;  //this is implied since ACTIVE is initial state
340     // Expires is set after object is created when the associated
341     // lease is constructed.
342     }
343 
344 
345     /**
346      * Convenience method which adds a given <code>ParticipantHandle</code> 
347      * to the set of <code>ParticpantHandle</code>s associated with this
348      * transaction.
349      *
350      * @param handle The added handle
351      */
352     void add(ParticipantHandle handle)
353         throws InternalManagerException
354     {
355         if (operationsLogger.isLoggable(Level.FINER)) {
356             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
357             "add", handle);
358     }
359     
360     if (handle == null)
361         throw new NullPointerException("ParticipantHolder: add: " +
362                        "cannot add null handle");
363 
364         //NOTE: if the same participant re-joins, then that is
365         //      fine.
366 
367     try {
368             if (transactionsLogger.isLoggable(Level.FINEST)) {
369                 transactionsLogger.log(Level.FINEST,
370                 "Adding ParticipantHandle: {0}", handle);
371             }
372         parts.add(handle);
373     } catch (Exception e) {
374             if (transactionsLogger.isLoggable(Level.SEVERE)) {
375                 transactionsLogger.log(Level.SEVERE,
376                 "Unable to add ParticipantHandle", e);
377         }
378         throw new InternalManagerException("GroupTxnManagerTransaction: " +
379                                  "add: " + e.getMessage());
380     }
381         if (operationsLogger.isLoggable(Level.FINER)) {
382             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
383             "add");
384     }
385     }
386 
387     /**
388      * Convenience method which allows the caller to modify the
389      * prepState associated with a given <code>ParticipantHandle</code>
390      *
391      * @param handle The <code>ParticipantHandle</code> being modified
392      *
393      * @param state The new prepstate
394      */
395     void modifyParticipant(ParticipantHandle handle, int state) {
396         if (operationsLogger.isLoggable(Level.FINER)) {
397             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
398             "modifyParticipant", new Object[] {handle, new Integer(state)});
399     }
400     ParticipantHandle ph = null;
401 
402     if (handle == null)
403         throw new NullPointerException("ParticipantHolder: " +
404             "modifyParticipant: cannot modify null handle");
405 
406     if (parts.contains(ph))
407         ph = (ParticipantHandle) parts.get(parts.indexOf(handle));  
408 
409     if (ph == null) {
410             if (operationsLogger.isLoggable(Level.FINER)) {
411                 operationsLogger.exiting(
412                 GroupTxnManagerTransaction.class.getName(), 
413             "modifyParticipant");
414         }
415 //TODO - ignore??       
416         return;
417     }
418 
419     ph.setPrepState(state);
420         if (operationsLogger.isLoggable(Level.FINER)) {
421             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
422             "modifyParticipant");
423     }
424     }
425 
426 
427     /**
428      * Changes the manager-side state of the transaction.  This
429      * method makes only valid state changes and informs the
430      * caller if the change was successful. Calls to this method
431      * synchronize around the manager-side state variable appropriately.
432      *
433      * @param int state the new desired state
434      */
435     boolean modifyTxnState(int state) {
436         if (operationsLogger.isLoggable(Level.FINER)) {
437             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
438             "modifyTxnState", new Integer(state));
439     }
440     boolean result = false;
441     synchronized (stateLock) {
442         switch (state) {
443         case ACTIVE:
444         case VOTING:
445         case COMMITTED:
446         case ABORTED:
447               result = states[trstate][state];
448           break;
449 
450           default:
451             throw new IllegalArgumentException("GroupTxnManagerTransaction: " +
452                             "modifyTxnState: invalid state");
453         }
454 
455         if (result)
456         trstate = state;
457     }
458         if (operationsLogger.isLoggable(Level.FINER)) {
459             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
460             "modifyTxnState", Boolean.valueOf(result));
461     }
462     return result;
463     }
464 
465 
466     /**
467      * Implementation of the join method. 
468      *
469      * @param part The joining <code>TransactionParticpant</code>
470      *
471      * @param crashCount The crashcount associated with the joining
472      *           <code>TransactionParticipant</code>
473      *
474      * @see net.jini.core.transaction.server.TransactionParticipant
475      */
476     public void
477     join(TransactionParticipant preparedPart, long crashCount)
478         throws CannotJoinException, CrashCountException, RemoteException
479     {
480         if (operationsLogger.isLoggable(Level.FINER)) {
481             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
482             "join", new Object[] {preparedPart, new Long(crashCount)});
483     }
484     //if the lease has expired, or the state is not
485     //amenable there is no need to continue
486 
487     if (getState() != ACTIVE)
488         throw new CannotJoinException("not active");
489 
490     if ((getState() == ACTIVE) && (ensureCurrent() == false)) {
491         doAbort(0);
492         throw new CannotJoinException("Lease expired");
493     }
494 
495     //Create a ParticipantHandle for the new participant
496     //and mark the transactional state as ACTIVE
497     try {
498         ParticipantHandle ph = 
499             new ParticipantHandle(preparedPart, crashCount);
500         ParticipantHandle phtmp = (ParticipantHandle)   
501             ((parts.contains(ph))?
502             parts.get(parts.indexOf(ph)):
503             null);  
504 
505             if (transactionsLogger.isLoggable(Level.FINEST)) {
506                 transactionsLogger.log(Level.FINEST,
507                 "Retrieved ParticipantHandle: {0}", phtmp);
508             }
509         if (phtmp != null) {
510         long oldcount = phtmp.getCrashCount();
511         if (oldcount == crashCount) {
512             return;
513         } else {
514             throw new CrashCountException("GroupTxnManagerTransaction: " +
515                         "join: old = " + oldcount +
516                         " new = " + crashCount);
517         }
518         }
519 
520         add(ph);
521 
522     } catch (InternalManagerException ime) {
523             if (transactionsLogger.isLoggable(Level.SEVERE)) {
524                 transactionsLogger.log(Level.SEVERE,
525                 "TransactionParticipant unable to join", ime);
526             }
527         throw ime;
528     } catch (RemoteException re) {
529             if (transactionsLogger.isLoggable(Level.FINEST)) {
530                 transactionsLogger.log(Level.FINEST,
531                 "TransactionParticipant unable to be stored", re);
532             }
533         throw re;
534     }
535         if (operationsLogger.isLoggable(Level.FINER)) {
536             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
537             "join");
538     }
539     }
540 
541 
542    /**
543     * This method returns the state of the transaction.
544     * Since the purpose of the set of ParticipantHolders is
545     * to associate a Transaction with a group of
546     * participants joined the transaction, we would like
547     * to get the state of the transaction associated with
548     * the aforementioned set.
549     *
550     */
551     public int getState() {
552         if (operationsLogger.isLoggable(Level.FINER)) {
553             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
554             "getState");
555     }
556     synchronized (stateLock) {
557             if (operationsLogger.isLoggable(Level.FINER)) {
558                 operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
559                 "getState", new Integer(trstate));
560         }
561             return trstate;
562     }
563     }
564 
565 
566     /**
567      * Commits the transaction.
568      * This initiates the two-phase commit protocol.  First,
569      * each <code>net.jini.core.transaction.server.TransactionParticipant</code>
570      * in the set of participants joined in the
571      * <code>net.jini.core.transaction.server.Transaction</code>
572      * is instructed to vote and the votes are tallied. This is the
573      * first phase (prepare phase).
574      *
575      * Depending on the outcome of the votes, the transaction
576      * is considered committed or aborted.  Once commit/abort
577      * status is known, the participants are notified with
578      * a message to either roll-forward (commit case) or 
579      * roll-back (abort case).  This is the roll-phase.
580      * 
581      * Since there may be a one-to-many relationship between
582      * a transaction and its participants,
583      * <code>com.sun.jini.thread.TaskManager</code>s are used
584      * as a generic mechanism to provide the threads needed
585      * to interact with the participants.
586      * 
587      * ** GAHALO **
588      * Modified to support passive replication.
589      */
590     void commit(long waitFor)
591         throws CannotCommitException, TimeoutExpiredException, RemoteException
592     {
593        if (log.isDebugEnabled())
594        log.debug("entering TMT.commit()");
595 
596         if (operationsLogger.isLoggable(Level.FINER)) {
597             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
598             "commit", new Long(waitFor));
599     }
600     long starttime = System.currentTimeMillis();
601 
602     //If the transaction has already expired or the state
603     //is not amenable, don't even try to continue
604 
605     if ((getState() == ACTIVE) && (ensureCurrent() == false)) {
606         doAbort(0);
607         throw new CannotCommitException("Lease expired");
608     }
609 
610     if (getState() == ABORTED)
611         throw new CannotCommitException("attempt to commit " +
612                         "ABORTED transaction");
613 
614 
615     //Check to see if anyone joined the transaction.  Even
616     //if no one has joined, at this point, attempt to
617     //get to the COMMITTED state through valid state changes
618 
619         Vector joinvec = parthandles();
620  
621         if (joinvec == null) {
622         if (!modifyTxnState(VOTING))
623         throw new CannotCommitException("attempt to commit " +
624                         "ABORTED transaction");
625 
626         if (modifyTxnState(COMMITTED))
627                 return;
628         else
629         throw new CannotCommitException("attempt to commit " +
630                         "ABORTED transaction");
631         }
632 
633     try {
634         Enumeration joined = joinvec.elements();
635         int numparts = joinvec.size();
636         ParticipantHandle[] phs = new ParticipantHandle[numparts];
637         joinvec.copyInto(phs);
638 
639             long now = starttime;
640             long transpired = 0;
641             long remainder = 0;
642 
643         ClientLog log = logmgr.logFor(str.id);
644 
645             if (transactionsLogger.isLoggable(Level.FINEST)) {
646                 transactionsLogger.log(Level.FINEST,
647                 "{0} TransactionParticipants have joined", 
648         new Integer(numparts));
649             }
650 
651         //If commit is called after recovery, do not
652         //log a CommitRecord since it already exists
653         //exists in the Log file for this transaction.
654         //Remember that a log is not invalidated until
655         //after the transaction is committed.
656         //
657         //Only new occurrences of activities requiring
658         //logging which happen after recovery should
659         //be added to the log file.  So, we add records
660         //for voting and roll forward/back activity for
661         //ACTIVE participants.
662         //
663         //If the state cannot validly transition to VOTING,
664         //it is either because someone already aborted or
665         //committed.  Only throw an exception if  someone
666         //has previously aborted.  In the case of a prior
667         //commit, fall through and wait for the CommitJob
668         //to complete.
669 
670         int oldstate = getState();
671         Integer result = new Integer(ABORTED);
672             Exception alternateException = null;
673 
674 
675         //On an ACTIVE to VOTING transition, create
676         //and schedule a Prepare or PrepareAndCommitJob.
677         //If the state transition is VOTING to VOTING,
678         //then the PrepareJob has already been created
679         //in the past, so just fall through and wait
680         //for it to complete.
681         //
682         //Only log the commit on ACTIVE to VOTING
683         //transitions.
684 
685         if (modifyTxnState(VOTING)) {
686 
687         if (oldstate == ACTIVE)
688             log.write(new CommitRecord(phs));
689 
690 
691         //preparing a participant can never override
692         //the other activities (abort or commit),
693         //so only set when the job is null.
694 
695         synchronized (jobLock) {
696           if (job == null) {
697             // We want to replicate the ready_to_prepare replies from tp to the
698             // backups
699             // if (phs.length == 1)
700             // job = new PrepareAndCommitJob(str, threadpool, wm, log, phs[0]);
701             // else
702             job = new PrepareJob(str, threadpool, wm, log, phs);
703             /*
704              * try { System.out.print("Sleeping before prepare");
705              * Thread.sleep(5000); } catch (InterruptedException ie) { }
706              */
707             // System.out.println("scheduling preparejobs!");
708             job.scheduleTasks();
709           }
710         }
711 
712         //Wait for the PrepareJob to complete.
713         //PrepareJobs are given maximum time for
714         //completion.  This is required in order to
715         //know the transaction's completion status.
716         //Remember that the timeout ONLY controls how
717         //long the caller is willing to wait to inform
718         //participants.  This means that a completion
719         //status for the transaction MUST be computed
720         //before consulting the timeout.
721         //Timeout is ignored until completion status
722         //is known.  If extra time is left, wait for
723         //the remainder to inform participants.
724 
725         //We must explicitly check for Job type
726         //because someone else could have aborted
727         //the transaction at this point.
728 
729 
730         synchronized (jobLock) {
731           if ((job instanceof PrepareJob) || (job instanceof PrepareAndCommitJob)) {
732             try {
733               if (job.isCompleted(Long.MAX_VALUE))
734                 result = (Integer) job.computeResult();
735             } catch (JobNotStartedException jnse) {
736               // no participants voted, so do nothing
737               result = new Integer(NOTCHANGED);
738             } catch (ResultNotReadyException rnre) {
739               // consider aborted
740             } catch (JobException je) {
741               // consider aborted
742             }
743           }
744         }
745         // System.out.println("ALL PARTS VOTED!");
746       } else {
747         // Cannot be VOTING, so we either have
748         // an abort or commit in progress.
749 
750         if (getState() == ABORTED)
751           throw new CannotCommitException("transaction ABORTED");
752 
753         // If a CommitJob is already in progress
754         // (the state is COMMITTED) cause a fall
755         // through to the code which waits for
756         // the CommitJob to complete.
757 
758         if (getState() == COMMITTED)
759           result = new Integer(COMMITTED);
760       }
761 
762       if (transactionsLogger.isLoggable(Level.FINEST)) {
763         transactionsLogger.log(Level.FINEST, "Voting result: {0}", TxnConstants.getName(result
764             .intValue()));
765       }
766 
767       switch (result.intValue()) {
768         case NOTCHANGED:
769           break;
770 
771         case ABORTED:
772           now = System.currentTimeMillis();
773           transpired = now - starttime;
774           remainder = waitFor - transpired;
775 
776           if (remainder >= 0)
777             doAbort(remainder);
778           else
779             doAbort(0);
780 
781           throw new CannotCommitException("Unable to commit " + "transaction");
782 
783         case PREPARED:
784           // This entrypoint is entered if a PrepareJob
785           // tallied the votes with an outcome of
786           // PREPARED. In order to inform participants,
787           // a CommitJob must be scheduled.
788 
789           if (modifyTxnState(COMMITTED)) {
790 
791             // Notify backups of transaction outcome, and
792             // if (passivelyReplicated)
793             allParticipantsPrepared();
794 
795             // TODO - log committed state record?
796 
797             synchronized (jobLock) {
798               job = new CommitJob(str, threadpool, wm, log, phs);
799               /*
800                * try { System.out.print("Sleeping before commit");
801                * Thread.sleep(5000); } catch (InterruptedException ie) { }
802                */
803               job.scheduleTasks();
804             }
805           } else {
806             throw new CannotCommitException("attempt to commit " + "ABORTED transaction");
807           }
808 
809         // Fall through to code with waits
810         // for CommitJob to complete.
811 
812         case COMMITTED:
813           // This entrypoint is the starting place for the code
814           // which waits for a CommitJob to complete and
815           // computes its resulting outcome. In addition,
816           // the wait time is enforced. Should the wait time
817           // expire, a SettlerTask is scheduled on a thread
818           // pool. The SettlerTask is needed to complete
819           // the commit (instruct participants to roll-forward)
820           // on behalf of the thread which exits when
821           // the TimeoutExpiredException is thrown.
822           //
823           // It is reached when...
824           //
825           // a) A commit was called on the same transaction twice.
826           // When the thread comes in on the commit call, a
827           // CommitJob already exists and the state is COMMITTED.
828           //
829           // b) The normal case where a PrepareJob was found to
830           // have prepared the transaction and the tally of
831           // votes resulted in a PREPARED outcome. This causes
832           // the state to be changed to COMMITTED and a
833           // CommitJob to be created.
834           //
835           // c) A PrepareAndCommitJob has successfully prepared
836           // a participant which rolled its changes forward.
837           //
838           // Note: By checking to see if the CommitJob is already
839           // present, this check allows us to use the same
840           // wait-for-CommitJob code for the regular
841           // PREPARE/COMMIT, the COMMIT/COMMIT and
842           // the PREPAREANDCOMMIT cases.
843 
844           synchronized (jobLock) {
845             // A prepareAndCommitJob is done at this
846             // point since the TransactionParticipant
847             // would have instructed itself to roll
848             // forward.
849 
850             if (job instanceof PrepareAndCommitJob) {
851               if (!modifyTxnState(COMMITTED))
852                 throw new CannotCommitException("transaction " + "ABORTED");
853               break;
854             }
855 
856             // If the abort already arrived, then stop
857 
858             if (job instanceof AbortJob)
859               throw new CannotCommitException("transaction " + "ABORTED");
860           }
861 
862           if (getState() != COMMITTED)
863             throw new InternalManagerException("GroupTxnManagerTransaction: " + "commit: "
864                 + job + " got bad state: " + TxnConstants.getName(result.intValue()));
865 
866           now = System.currentTimeMillis();
867           transpired = now - starttime;
868 
869           boolean committed = false;
870 
871           // If the commit is asynchronous then...
872           //
873           // a) check to see if the wait time has transpired
874           //
875           // b) If it hasn't, sleep for what's left from the wait time
876 
877           try {
878             remainder = waitFor - transpired;
879 
880             synchronized (jobLock) {
881 
882               if (remainder <= 0 || !job.isCompleted(remainder)) {
883 
884                 /*
885                  * Note - SettlerTask will kick off another Commit/Abort task
886                  * for the same txn which will try go through the VOTING->Commit
887                  * states again.
888                  */
889                 // TODO - Kill off existing task? Postpone SettlerTask?
890                 settler.noteUnsettledTxn(str.id);
891 
892                 throw new TimeoutExpiredException("timeout expired", true);
893               } else {
894                 result = (Integer) job.computeResult();
895 
896                 // Check if passive replication is used
897                 // if (passivelyReplicated) {
898                 // Check if backups already have been notified of the completed
899                 // commit
900                 if (!backupsCommitted) {
901 
902                   // If not, notify them
903                   // if (Debug.DEBUG)
904                   // this.log.debug("All backups are not committed");
905                   allParticipantsCommitted();
906                   backupsCommitted = true;
907                 } else {
908                   // if (Debug.DEBUG)
909                   // this.log.debug("All backups are committed");
910                 }
911                 // }
912                 committed = true;
913               }
914             }
915           } catch (ResultNotReadyException rnre) {
916             // this should not happen, so flag
917             // as an error.
918           } catch (JobNotStartedException jnse) {
919             // an error
920           } catch (JobException je) {
921             // an error
922           }
923 
924           if (committed)
925             break;
926 
927         default:
928           throw new InternalManagerException("GroupTxnManagerTransaction: " + "commit: " + job
929               + " got bad state: " + TxnConstants.getName(result.intValue()));
930       }
931 
932       // We don't care about the result from
933       // the CommitJob
934 
935       log.invalidate();
936     } catch (RuntimeException rte) {
937       if (transactionsLogger.isLoggable(Level.FINEST)) {
938         transactionsLogger.log(Level.FINEST, "Problem committing transaction", rte);
939       }
940       throw rte;
941     } catch (LogException le) {
942       if (transactionsLogger.isLoggable(Level.FINEST)) {
943         transactionsLogger.log(Level.FINEST, "Problem persisting transaction", le);
944       }
945       throw new CannotCommitException("Unable to log");
946     }
947     if (operationsLogger.isLoggable(Level.FINER)) {
948       operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), "commit");
949     }
950   }
951 
952     /**
953      * Aborts the transaction.
954      * This method attempts to set the state of the transaction
955      * to the ABORTED state.  If successful, it then creates
956      * an AbortJob which schedules tasks executed on a
957      * thread pool.  These tasks interact with the participants
958      * joined in this transaction to inform them to roll back.
959      * 
960      * @param waitFor Timeout value which controls how long,
961      *            the caller is willing to wait for the
962      *            participants joined in the transaction 
963      *            to be instructed to roll-back.
964      *
965      * @see com.sun.jini.mahalo.AbortJob
966      * @see com.sun.jini.mahalo.ParticipantTask
967      * @see com.sun.jini.thread.TaskManager
968      * @see net.jini.core.transaction.server.TransactionParticipant
969      */
970     void abort(long waitFor)
971     throws CannotAbortException, TimeoutExpiredException
972     {
973         if (operationsLogger.isLoggable(Level.FINER)) {
974             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
975             "abort", new Long(waitFor));
976     }
977     long starttime = System.currentTimeMillis();
978 
979     /*
980      * Since lease cancellation process sets expiration to 0 
981      * and then calls abort, can't reliably check expiration 
982      * at this point.
983      */
984 //TODO - Change internal, lease logic to call overload w/o expiration check
985 //TODO - Add expiration check to abort for external clients     
986     try {
987             Vector joinvec = parthandles();
988 
989         if (joinvec == null) {
990         if (modifyTxnState(ABORTED))
991             return;
992         else 
993             throw new
994             CannotAbortException("Transaction already COMMITTED");
995         }
996 
997             Enumeration joined = joinvec.elements();
998             int numparts = joinvec.size();
999             ParticipantHandle[] phs = new ParticipantHandle[numparts];
1000             joinvec.copyInto(phs);
1001 
1002             ClientLog log = logmgr.logFor(str.id);
1003 
1004 
1005         //When attempting to abort, if someone has already
1006         //committed the transaction, then throw an Exception.
1007         //
1008         //If an abort is possible, and you find that an AbortJob
1009         //is already in progress, let the existing AbortJob
1010         //proceed.
1011         //
1012         //If an abort is possible, but a PrepareJob is in progress,
1013         //go ahead an halt the PrepareJob and replace it with
1014         //an AbortJob.
1015 
1016         if (modifyTxnState(ABORTED)) {
1017                 log.write(new AbortRecord(phs));
1018 
1019             synchronized (jobLock) {
1020                 if (!(job instanceof AbortJob)) {
1021                 if (job != null)
1022                     job.stop();
1023                     job = new AbortJob(str, threadpool, wm, log, phs);
1024                     job.scheduleTasks();
1025                 }
1026             }
1027         } else {
1028         throw new CannotAbortException("Transaction already COMMITTED");
1029         }
1030 
1031 
1032         //This code waits for an AbortJob to complete and
1033         //computes its resulting outcome. In addition,
1034         //the wait time is enforced.  Should the wait time
1035         //expire, a SettlerTask is scheduled on a thread
1036         //pool.  The SettlerTask is needed to complete
1037         //the abort (instruct participants to roll-back)
1038         //on behalf of the thread which exits when
1039         //the TimeoutExpiredException is thrown.
1040 
1041         long now = System.currentTimeMillis();
1042         long transpired = now - starttime;
1043 
1044         Integer result = new Integer(ACTIVE);
1045         boolean aborted = false;
1046 
1047         long remainder = waitFor - transpired;
1048 
1049         try {
1050         synchronized (jobLock) {
1051                 if (remainder<= 0 || !job.isCompleted(remainder)) {
1052                 settler.noteUnsettledTxn(str.id);
1053                 throw new TimeoutExpiredException(
1054                         "timeout expired",false);
1055             } else {
1056                     result = (Integer) job.computeResult();
1057                     aborted = true;
1058             }
1059         }
1060         }  catch (ResultNotReadyException rnre) {
1061         //should not happen, so flag as error
1062         } catch (JobNotStartedException jnse) {
1063         //error
1064         } catch (JobException je) {
1065             settler.noteUnsettledTxn(str.id);
1066         throw new TimeoutExpiredException("timeout expired", false);
1067         }
1068 
1069 
1070         if (!aborted)
1071                 throw new InternalManagerException("GroupTxnManagerTransaction: " +
1072                                 "abort: AbortJob got bad state: " +
1073                         TxnConstants.getName(result.intValue()));
1074 
1075         log.invalidate();
1076         } catch (RuntimeException rte) {
1077             if (transactionsLogger.isLoggable(Level.SEVERE)) {
1078                 transactionsLogger.log(Level.SEVERE,
1079                 "Problem aborting transaction", 
1080              rte);
1081             }
1082         throw new InternalManagerException("GroupTxnManagerTransaction: " +
1083                             "abort: fatal error");
1084         } catch (LogException le) {
1085             if (transactionsLogger.isLoggable(Level.FINEST)) {
1086                 transactionsLogger.log(Level.FINEST,
1087                 "Problem persisting transaction", 
1088              le);
1089             }
1090             throw new CannotAbortException("Unable to log");
1091         }
1092         if (operationsLogger.isLoggable(Level.FINER)) {
1093             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
1094             "abort");
1095     }
1096     }
1097 
1098     public Transaction getTransaction() {
1099         if (operationsLogger.isLoggable(Level.FINER)) {
1100             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
1101             "getTransaction");
1102     }
1103         if (operationsLogger.isLoggable(Level.FINER)) {
1104             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
1105             "getTransaction", str);
1106     }
1107     return str;
1108     }
1109 
1110     public long getExpiration() {
1111         if (operationsLogger.isLoggable(Level.FINER)) {
1112             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
1113             "getExpiration");
1114     }
1115     synchronized (leaseLock) {
1116             if (operationsLogger.isLoggable(Level.FINER)) {
1117                 operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
1118                 "getExpiration", new Date(expires));
1119         }
1120             return expires;
1121     }
1122     }
1123 
1124     public void setExpiration(long newExpiration) {
1125         if (operationsLogger.isLoggable(Level.FINER)) {
1126             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
1127             "setExpiration", new Date(newExpiration));
1128     }
1129     synchronized (leaseLock) {
1130         expires = newExpiration;
1131     }
1132         if (operationsLogger.isLoggable(Level.FINER)) {
1133             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
1134             "setExpiration");
1135     }
1136     }
1137 
1138     public Uuid getCookie() {
1139         if (operationsLogger.isLoggable(Level.FINER)) {
1140             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
1141             "getCookie");
1142     }
1143         if (operationsLogger.isLoggable(Level.FINER)) {
1144             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
1145             "getCookie", uuid);
1146     }
1147     return uuid;
1148     }
1149 
1150     private void doAbort(long timeout) {
1151         if (operationsLogger.isLoggable(Level.FINER)) {
1152             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
1153             "doAbort", new Long(timeout));
1154     }
1155         try {
1156             str.abort(timeout);
1157         } catch (RemoteException re) {
1158             //abort must have happened, so ignore
1159             if (transactionsLogger.isLoggable(Levels.HANDLED)) {
1160                 transactionsLogger.log(Levels.HANDLED,
1161                 "Trouble aborting  transaction", re);
1162         }
1163         } catch (TimeoutExpiredException te) {
1164         //Swallow this because we really only
1165         //care about a scheduling a SettlerTask
1166             if (transactionsLogger.isLoggable(Levels.HANDLED)) {
1167                 transactionsLogger.log(Levels.HANDLED,
1168                 "Trouble aborting  transaction", te);
1169         }
1170         } catch (TransactionException bte) {
1171             //If abort has problems, swallow
1172             //it because the abort must have
1173             //happened
1174             if (transactionsLogger.isLoggable(Levels.HANDLED)) {
1175                 transactionsLogger.log(Levels.HANDLED,
1176                 "Trouble aborting  transaction", bte);
1177         }
1178     }
1179         if (operationsLogger.isLoggable(Level.FINER)) {
1180             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
1181             "doAbort");
1182     }
1183 
1184     }
1185 
1186     synchronized boolean ensureCurrent() {
1187         if (operationsLogger.isLoggable(Level.FINER)) {
1188             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
1189             "ensureCurrent");
1190     }
1191     long cur = System.currentTimeMillis();
1192     boolean result = false;
1193     long useby = getExpiration();
1194 
1195     if (useby > cur)
1196         result = true;
1197         if (operationsLogger.isLoggable(Level.FINER)) {
1198             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
1199             "ensureCurrent", Boolean.valueOf(result));
1200     }
1201     return result;
1202     }
1203 
1204 
1205     private Vector parthandles() {
1206         if (operationsLogger.isLoggable(Level.FINER)) {
1207             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
1208             "parthandles");
1209     }
1210     if ( (parts == null ) || ( parts.size() == 0 ) )
1211         return null;
1212 
1213         Vector vect = new Vector(parts);
1214  
1215         if (transactionsLogger.isLoggable(Level.FINEST)) {
1216             transactionsLogger.log(Level.FINEST,
1217             "Retrieved {0} participants", 
1218          new Integer(vect.size()));
1219         }
1220     
1221         if (operationsLogger.isLoggable(Level.FINER)) {
1222             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
1223             "parthandles");
1224     }
1225 
1226     return vect;
1227     }
1228     
1229     private String getParticipantInfo() {
1230         if (operationsLogger.isLoggable(Level.FINER)) {
1231             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
1232             "getParticipantInfo");
1233     }
1234         if ( (parts == null ) || ( parts.size() == 0 ) )
1235         return "No participants";
1236 
1237         if (transactionsLogger.isLoggable(Level.FINEST)) {
1238             transactionsLogger.log(Level.FINEST,
1239             "{0} participants joined", new Integer(parts.size()));
1240         }
1241     StringBuffer sb = new StringBuffer(parts.size() + " Participants: ");
1242         ParticipantHandle ph;
1243         for (int i=0; i < parts.size(); i++) {
1244             ph = (ParticipantHandle)parts.get(i);
1245             sb.append(
1246                 "{" + i + ", " 
1247                 + ph.getPreParedParticipant().toString() + ", " 
1248                 + TxnConstants.getName(ph.getPrepState())
1249                 + "} ");
1250         }
1251         if (operationsLogger.isLoggable(Level.FINER)) {
1252             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
1253             "getParticipantInfo", sb.toString());
1254     }
1255 
1256     return sb.toString();
1257     }
1258     
1259     void restoreTransientState(ProxyPreparer preparer) 
1260         throws RemoteException
1261     {
1262         if (operationsLogger.isLoggable(Level.FINER)) {
1263             operationsLogger.entering(GroupTxnManagerTransaction.class.getName(), 
1264             "restoreTransientState");
1265     }
1266         if ( (parts == null ) || ( parts.size() == 0 ) )
1267         return;
1268 
1269     ParticipantHandle[] handles = (ParticipantHandle[])
1270         parts.toArray(new ParticipantHandle[parts.size()]);
1271         for (int i=0; i < handles.length; i++) {
1272         handles[i].restoreTransientState(preparer);
1273             if (transactionsLogger.isLoggable(Level.FINEST)) {
1274                 transactionsLogger.log(Level.FINEST,
1275                 "Restored transient state for {0}", 
1276              handles[i]);
1277             }
1278     }
1279     
1280         if (operationsLogger.isLoggable(Level.FINER)) {
1281             operationsLogger.exiting(GroupTxnManagerTransaction.class.getName(), 
1282             "restoreTransientState");
1283     }
1284     }
1285 
1286     
1287     ////////////////////////////////////////////////////////////////////////////////////////////
1288     // ** GAHALO ** Passive replication methods
1289     ////////////////////////////////////////////////////////////////////////////////////////////
1290     
1291     /**
1292      * Distributes commit decision to all backups
1293      */
1294     private void allParticipantsPrepared()
1295     {
1296       if (log.isDebugEnabled())
1297         log.debug("Distribute all-participants-prepares message to backups");
1298 
1299       InternalPassiveGroupTransactionManager ipgtm = PassiveGroupMahalo.getIPGTM();
1300       if (ipgtm == null) {
1301         log.debug("ipgtm == null");
1302         System.exit(0);
1303       }
1304       try {
1305         /** distribute transaction object and transaction id to followers */
1306         startprepare = System.nanoTime();
1307         ipgtm.transPrepared(this, id);
1308         
1309       } catch (RemoteException e) {
1310         log.error("Could not distribute transaction commit to backups", e);
1311         e.printStackTrace();
1312         System.exit(0);
1313       } catch (Exception e) {
1314         log.error("Error trying to prepare backup replicas: ", e);
1315         System.exit(0);
1316       }
1317 
1318       log.debug("OK - All transaction participants prepared");
1319       endprepare = System.nanoTime();
1320     }
1321 
1322     /**
1323      * Distributes the finalization of the transaction commit to all backups
1324      */
1325     private void allParticipantsCommitted()
1326     {
1327       if (log.isDebugEnabled())
1328         log.debug("Distribute all-participants-committed message to backups");
1329       
1330       InternalPassiveGroupTransactionManager ipgtm = PassiveGroupMahalo.getIPGTM();
1331 
1332       try {
1333         /** distribute transaction completion to backups */
1334         startcommit = System.nanoTime();
1335         ipgtm.transCommitted(id);
1336 
1337       } catch (RemoteException e) {
1338         log.error("Could not distribute transaction commit to backups", e);
1339       } catch (UnknownTransactionException e) {
1340         log.error("Transaction backups did not know of transaction id: " + id, e);
1341       }
1342 
1343       log.debug("OK - All transaction participants committed");
1344       endcommit = System.nanoTime();
1345       log.debug(((endprepare - startprepare)) / 1000000 + "\t"
1346           + ((endcommit - startcommit) / 1000000));
1347     }
1348     
1349     /** Return the ID of this transaction */
1350     protected long getID() {
1351       return id;
1352     }
1353     
1354     /**
1355      * Setter method for the LogManager.
1356      */
1357     protected void setLogManager(LogManager logmgr) 
1358     {
1359       this.logmgr = logmgr;
1360     }
1361     
1362     /**
1363      * Setter method for the TaskManager.
1364      */
1365     protected void setTaskManager(TaskManager taskmgr) 
1366     {
1367       this.threadpool = taskmgr;
1368     }
1369     
1370     /**
1371      * Setter method for WakeupManager.
1372      */
1373     protected void setWakeupManager(WakeupManager wakeupMgr) 
1374     {
1375       this.wm = wakeupMgr;
1376     }
1377     
1378     /**
1379      * Setter method for TxnSettler.
1380      */
1381     protected void setSettler(TxnSettler settler) 
1382     {
1383       this.settler = settler;
1384     }
1385 
1386     /**
1387      * Setter method for the locks.
1388      */
1389     protected void setLocks() 
1390     {
1391       leaseLock = new Object();
1392       jobLock = new Object();
1393       stateLock = new Object();
1394     }
1395     
1396 } // END GroupTxnManagerTransaction