comparison src/org/dancres/blitz/txn/TxnState.java @ 0:3dc0c5604566

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:3dc0c5604566
1 package org.dancres.blitz.txn;
2
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.Iterator;
6 import java.util.logging.Level;
7
8 import net.jini.core.transaction.TransactionException;
9 import net.jini.core.transaction.UnknownTransactionException;
10 import net.jini.core.transaction.server.TransactionConstants;
11
12 import org.dancres.blitz.disk.DiskTxn;
13 import org.dancres.blitz.notify.EventQueue;
14 import org.dancres.blitz.notify.QueueEvent;
15 import org.dancres.blitz.task.Task;
16 import org.dancres.blitz.task.Tasks;
17
18 /*
19 * We only need to post QueueEvent.TRANSACTION_ENDED during live work, not
20 * during recovery as it will only be relevant to active notifies. When
21 * we restart the only active notifies left would be those not interested in
22 * the event because they were registered with a null transaction.
23 *
24 * Given the above, we can post the event from doFinalize which will be
25 * called in TxnManager as part of "live" operations. This neatly ensures
26 * we will only do this work outside of recovery.
27 *
28 * We will post TRANSACTION_ENDED even for null transactions and for each
29 * such event scan the generators and kill any that are anchored against
30 * the transaction - this should be done via a new method to test this.
31 *
32 * EventGenerators should only profess interest if they aren't already tainted
33 * as they do with canSee etc.
34 *
35 * Note we needn't call kill we could just pass the event into all generators
36 * and have them taint and then kill themselves (assuming they no longer
37 * dispatch CleanTask). We could then strip out TxnNotify's but we would need
38 * to add support for TRANSACTION_ENDED to each canSee method.
39 */
40
41 /**
42 Contains all state associated with a particular transaction identified
43 by TxnId.
44
45 @todo Deal with TRANSACTION_ENDED etc as above
46 */
47 public class TxnState implements java.io.Serializable {
48 static final long serialVersionUID = 2855252304868267667L;
49
50 private int theState = TransactionConstants.ACTIVE;
51
52 private TxnId theId;
53
54 private boolean nonDestructive;
55
56 /*
57 Typical transaction might be take/write or a few ops more, so we
58 allocate accordingly
59
60 @todo Decide if this is appropriate/worth doing
61 */
62 private ArrayList theOperations = new ArrayList(5);
63
64 TxnState(TxnId anId) {
65 theId = anId;
66 }
67
68 TxnState(TxnId anId, boolean isIdentity) {
69 theId = anId;
70 nonDestructive = isIdentity;
71 }
72
73 public TxnId getId() {
74 return theId;
75 }
76
77 int getStatus() throws TransactionException {
78 synchronized(this) {
79 return theState;
80 }
81 }
82
83 public void add(TxnOp anOp) throws TransactionException {
84 synchronized (this) {
85 if (theState != TransactionConstants.ACTIVE) {
86 TxnManager.theLogger.log(Level.SEVERE,
87 "Txn not in active state at addOp");
88 throw new TransactionException("Transaction no longer active");
89 } else {
90 theOperations.add(anOp);
91 }
92 }
93 }
94
95 /**
96 * A move to voting state indicates this transaction is going to be resolved
97 * and thus cannot have any further actions assigned to it. Note that
98 * voting state will only be asserted if the transaction is currently
99 * active. Voting is a transient state and only exists temporarily whilst
100 * we move to a resolved state. It's only benefit is that it renders
101 * a transaction's state unchangeable and allows us to make binding
102 * decisions such as whether we need to log to disk.
103 */
104 int vote() throws TransactionException {
105 int myState;
106
107 synchronized (this) {
108
109 if (theState == TransactionConstants.ACTIVE) {
110 theState = TransactionConstants.VOTING;
111 }
112
113 myState = theState;
114 }
115
116 return myState;
117 }
118
119 /**
120 * @todo Decide if ths DiskTxn is required here - probably not?
121 */
122 int prepare(boolean needsRestore)
123 throws UnknownTransactionException, IOException {
124
125 if (needsRestore) {
126 /*
127 If we're doing restore, we're single threaded and don't need
128 the protection of the lock (getLock())
129 */
130 // DiskTxn myTxn = DiskTxn.newTxn();
131
132 for (int i = (theOperations.size() - 1); i > -1; i--) {
133 TxnOp myOp = (TxnOp) theOperations.get(i);
134 myOp.restore(this);
135 }
136
137 // myTxn.commit();
138 }
139
140 int myStatus;
141
142 synchronized(this) {
143 if (theState == TransactionConstants.VOTING) {
144 theState = TransactionConstants.PREPARED;
145 }
146
147 /*
148 The state may not have been ACTIVE, we might already have
149 been prepared or even got to commited/aborted and we need to
150 reflect that in the status we return.
151 */
152 myStatus = theState;
153
154 }
155
156 return myStatus;
157 }
158
159 void commit()
160 throws UnknownTransactionException, IOException {
161
162 synchronized(this) {
163 if (theState != TransactionConstants.PREPARED) {
164 TxnManager.theLogger.log(Level.SEVERE,
165 "Txn not prepared before commit");
166 throw new UnknownTransactionException();
167 }
168
169 /*
170 * We can afford to set the state beforehand because if this doesn't
171 * work we have serious problems and it's very unlikely a second attempt
172 * will succeed/do any good.
173 */
174 theState = TransactionConstants.COMMITTED;
175
176 /*
177 * Having changed the state we can be sure no one else will do the
178 * same thing again and it's better to not hold the lock across the
179 * below which might require I/O
180 */
181 }
182
183 /*
184 * MUST BE DONE IN REVERSE ORDER - WE WANT A TAKE TO BE APPLIED
185 * BEFORE WE HIT THE PREVIOUS WRITE. THIS ALLOWS US TO GENERATE
186 * APPROPRIATE EVENTS FOR WRITES READY FOR NOTIFY - i.e. we
187 * want the disk system to be aware that something has been
188 * deleted so that we can test that state and abandon generating
189 * a write event in those cases. This saves adding code/structure
190 * to search other operations to verify whether a write generates
191 * an event or not.
192 */
193
194 // StringBuffer myTxn = new StringBuffer(theId + " ");
195
196 for (int i = (theOperations.size() - 1); i > -1; i--) {
197 TxnOp myOp = (TxnOp) theOperations.get(i);
198 myOp.commit(this);
199
200 // myTxn = myTxn.append(myOp.toString() + " ");
201 }
202
203 // TxnManager.theLogger.log(Level.INFO, myTxn.toString());
204 }
205
206 void abort()
207 throws UnknownTransactionException, IOException {
208
209 synchronized(this) {
210 /*
211 Transaction pinger may attempt abort on a just finalized transaction
212 cos the transaction manager has forgotten about it. We need to
213 handle that by refusing to abort when we're finalized (committed
214 or aborted).
215 */
216 if ((theState == TransactionConstants.COMMITTED) ||
217 (theState == TransactionConstants.ABORTED)) {
218 TxnManager.theLogger.log(Level.SEVERE,
219 "Txn already finalized");
220 throw new UnknownTransactionException();
221 }
222
223 /*
224 * We can afford to set the state beforehand because if this doesn't
225 * work we have serious problems and it's very unlikely a second attempt
226 * will succeed/do any good.
227 */
228 theState = TransactionConstants.ABORTED;
229
230 /*
231 * Having changed the state we can be sure no one else will do the
232 * same thing again and it's better to not hold the lock across the
233 * below which might require I/O
234 */
235 }
236
237 Iterator myOps = theOperations.iterator();
238
239 // No events generated so we can use whatever order we like
240 while (myOps.hasNext()) {
241 TxnOp myOp = (TxnOp) myOps.next();
242 myOp.abort(this);
243 }
244 }
245
246 public boolean isIdentity() {
247 return nonDestructive;
248 }
249
250 public boolean isNull() {
251 return theId.isNull();
252 }
253
254 public boolean hasNoOps() {
255 return (theOperations.size() == 0);
256 }
257
258 public String toString() {
259 StringBuffer myOps = new StringBuffer();
260
261 for (int i = 0; i < theOperations.size(); i++) {
262 // For the first operation, we adopt the time of the
263 // enclosing command so drop the hyphen
264 if (i == 0)
265 myOps.append(" " + theOperations.get(i).toString() + " : " +
266 theId + "\n");
267 else
268 myOps.append("- : " + theOperations.get(i).toString() + " : " +
269 theId + "\n");
270 }
271
272 return myOps.toString();
273 }
274
275 void doFinalize() {
276 /*
277 * Signal any associated listeners - we only want to do this during live
278 * operations
279 */
280 QueueEvent myEvent =
281 new QueueEvent(QueueEvent.TRANSACTION_ENDED,
282 this, null);
283 EventQueue.get().add(myEvent);
284 }
285
286 public void test() throws TransactionException {
287 synchronized (this) {
288 if (theState != TransactionConstants.ACTIVE) {
289 TxnManager.theLogger.log(Level.SEVERE,
290 "Txn not in active state at addOp");
291 throw new TransactionException("Transaction no longer active");
292 }
293 }
294 }
295 }