1 | /* |
2 | |
3 | Derby - Class org.apache.derby.impl.store.raw.xact.TransactionTable |
4 | |
5 | Copyright 1997, 2004 The Apache Software Foundation or its licensors, as applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | package org.apache.derby.impl.store.raw.xact; |
22 | |
23 | import org.apache.derby.iapi.services.context.ContextManager; |
24 | |
25 | import org.apache.derby.iapi.services.sanity.SanityManager; |
26 | import org.apache.derby.iapi.services.io.Formatable; |
27 | import org.apache.derby.iapi.services.io.FormatIdUtil; |
28 | import org.apache.derby.iapi.services.io.StoredFormatIds; |
29 | |
30 | import org.apache.derby.iapi.store.access.TransactionInfo; |
31 | |
32 | import org.apache.derby.iapi.store.raw.GlobalTransactionId; |
33 | |
34 | import org.apache.derby.iapi.store.raw.log.LogInstant; |
35 | |
36 | import org.apache.derby.iapi.store.raw.xact.RawTransaction; |
37 | import org.apache.derby.iapi.store.raw.xact.TransactionId; |
38 | |
39 | import org.apache.derby.iapi.error.StandardException; |
40 | |
41 | import org.apache.derby.iapi.services.io.CompressedNumber; |
42 | |
43 | import java.util.Hashtable; |
44 | import java.util.Enumeration; |
45 | import java.io.ObjectOutput; |
46 | import java.io.ObjectInput; |
47 | import java.io.IOException; |
48 | |
49 | /** |
50 | The transaction table is used by the transaction factory to keep track of |
51 | all transactions that are in the system. |
52 | |
53 | <BR> The transction table serves the following purposes: <OL> |
54 | |
55 | <LI> checkpoint - when a checkpoint log record is written out, it writes |
56 | out also all transactions that have updated the database. RESOLVE: this is |
57 | actually not used right now - rather, the transaction table is |
58 | reconstructed during the redo phase by traversing from the undo LWM. It is |
59 | a goal to use this transaction table (and traversing from the redoLWM) |
60 | instead of rebuilding it to speed up recovery. |
61 | |
62 | <LI> Quiesce State - when a system enters the quiesce state, it needs to account |
63 | for all transactions in the system, even those which are just started and |
64 | are in their IDLE state. |
65 | |
66 | <LI> TransactionTable VTI - we need to get a snapshot of all transactions |
67 | in the system for diagnostic purposes. |
68 | </OL> |
69 | |
70 | In order to speed up the time it takes to look up a transaction from the |
71 | transaction table, each transaction must have a unique transaction Id. |
72 | This means newly coined transaction must also have a transaction Id. |
73 | |
74 | <P>During recovery, there is only one real xact object doing all the |
75 | recovery work, but there could be many outstanding transactions that are |
76 | gleamed from the log. Each of these "recovery transactions" have its on |
77 | entry into the transaction table but they all share the same Xact object. |
78 | |
79 | <P>Multithreading considerations:<BR> |
80 | TransactionTable must be MT-safe it is called upon by many threads |
81 | simultaneously (except during recovery) |
82 | |
83 | <P><B> This class depends on Hashtable synchronization!! </B> |
84 | |
85 | */ |
86 | |
87 | public class TransactionTable implements Formatable |
88 | { |
89 | /* |
90 | * Fields |
91 | */ |
92 | |
93 | private Hashtable trans; |
94 | |
95 | private TransactionId largestUpdateXactId; |
96 | |
97 | /** |
98 | MT - not needed for constructor |
99 | */ |
100 | public TransactionTable() |
101 | { |
102 | trans = new Hashtable(17); |
103 | } |
104 | |
105 | /************************************************************* |
106 | * generic methods called by all clients of transaction table |
107 | * Must be MT -safe |
108 | ************************************************************/ |
109 | private TransactionTableEntry findTransactionEntry(TransactionId id) |
110 | { |
111 | |
112 | if (SanityManager.DEBUG) |
113 | SanityManager.ASSERT( |
114 | id != null, "findTransacionEntry with null id"); |
115 | |
116 | // Hashtable is synchronized |
117 | return (TransactionTableEntry)trans.get(id); |
118 | } |
119 | |
120 | |
121 | |
122 | |
123 | void add(Xact xact, boolean exclude) |
124 | { |
125 | TransactionId id = xact.getId(); |
126 | |
127 | synchronized(this) |
128 | { |
129 | TransactionTableEntry ent = findTransactionEntry(id); |
130 | |
131 | if (ent == null) |
132 | { |
133 | ent = new TransactionTableEntry |
134 | (xact, id, 0, |
135 | exclude ? TransactionTableEntry.EXCLUDE : 0); |
136 | |
137 | trans.put(id, ent); |
138 | |
139 | if (SanityManager.DEBUG) |
140 | { |
141 | if (SanityManager.DEBUG_ON("TranTrace")) |
142 | { |
143 | SanityManager.DEBUG( |
144 | "TranTrace", "adding transaction " + id); |
145 | SanityManager.showTrace(new Throwable("TranTrace")); |
146 | } |
147 | } |
148 | } |
149 | |
150 | if (SanityManager.DEBUG) |
151 | { |
152 | if (exclude != ent.needExclusion()) |
153 | SanityManager.THROWASSERT( |
154 | "adding the same transaction with different exclusion: " + |
155 | exclude + " " + ent.needExclusion()); |
156 | } |
157 | } |
158 | |
159 | if (SanityManager.DEBUG) { |
160 | |
161 | if (SanityManager.DEBUG_ON("memoryLeakTrace")) { |
162 | |
163 | if (trans.size() > 50) |
164 | System.out.println("memoryLeakTrace:TransactionTable " + trans.size()); |
165 | } |
166 | } |
167 | } |
168 | |
169 | /* |
170 | remove the transaction Id an return false iff the transaction is found |
171 | in the table and it doesn't need exclusion during quiesce state |
172 | */ |
173 | boolean remove(TransactionId id) |
174 | { |
175 | if (SanityManager.DEBUG) |
176 | SanityManager.ASSERT( |
177 | id != null, |
178 | "cannot remove transaction from table with null id"); |
179 | |
180 | if (SanityManager.DEBUG) |
181 | { |
182 | if (SanityManager.DEBUG_ON("TranTrace")) |
183 | { |
184 | SanityManager.DEBUG( |
185 | "TranTrace", "removing transaction " + id); |
186 | SanityManager.showTrace(new Throwable("TranTrace")); |
187 | } |
188 | } |
189 | |
190 | // Hashtable is synchronized |
191 | TransactionTableEntry ent = (TransactionTableEntry)trans.remove(id); |
192 | return (ent == null || ent.needExclusion()); |
193 | } |
194 | |
195 | |
196 | /** |
197 | Change a transaction to update or add an update transaction to this table. |
198 | |
199 | @param tid the transaction id |
200 | @param tran the transaction to be added |
201 | @param transactionStatus the transaction status that is stored in the |
202 | BeginXact log record |
203 | */ |
204 | public void addUpdateTransaction(TransactionId tid, RawTransaction tran, |
205 | int transactionStatus) |
206 | { |
207 | |
208 | // we need to synchronize on the transaction table because we have to |
209 | // prevent this state change from happening when the transaction table |
210 | // itself is written out to the checkpoint. This is the only |
211 | // protection the TransactionTableEntry has to prevent fields in myxact |
212 | // from changing underneath it while it is being written out. |
213 | synchronized(this) |
214 | { |
215 | TransactionTableEntry ent = findTransactionEntry(tid); |
216 | |
217 | if (ent != null) |
218 | { |
219 | // this happens during run time, when a transaction that is |
220 | // already started changed status to an update transaction |
221 | |
222 | ent.updateTransactionStatus((Xact)tran, transactionStatus, |
223 | TransactionTableEntry.UPDATE) ; |
224 | } |
225 | else |
226 | { |
227 | // this happens during recovery, that's why we haven't seen |
228 | // this transaction before - it is added in the doMe of the |
229 | // BeginXact log record. |
230 | // |
231 | // No matter what this transaction is, it won't need to be run |
232 | // in quiesce state because we are in recovery. |
233 | ent = new TransactionTableEntry((Xact)tran, tid, transactionStatus, |
234 | TransactionTableEntry.UPDATE | |
235 | TransactionTableEntry.EXCLUDE | |
236 | TransactionTableEntry.RECOVERY); |
237 | trans.put(tid, ent); |
238 | |
239 | } |
240 | |
241 | if (XactId.compare(ent.getXid(), largestUpdateXactId) > 0) |
242 | largestUpdateXactId = ent.getXid(); |
243 | } |
244 | } |
245 | |
246 | /** |
247 | Change update transaction to non-update |
248 | |
249 | <P>MT - MT safe, since vector is MT-safe. |
250 | |
251 | @param id the transaction Id |
252 | */ |
253 | void removeUpdateTransaction(TransactionId id) |
254 | { |
255 | // we need to synchronize on the transaction table because we have to |
256 | // prevent this state change from happening when the transaction table |
257 | // itself is written out to the checkpoint. This is the only |
258 | // protection the TransactionTableEntry has to prevent fields in myxact |
259 | // from changing underneath it while it is being written out. |
260 | |
261 | synchronized (this) |
262 | { |
263 | TransactionTableEntry ent = findTransactionEntry(id); |
264 | |
265 | if (SanityManager.DEBUG) |
266 | { |
267 | SanityManager.ASSERT(ent != null, |
268 | "removing update transaction that is not there"); |
269 | } |
270 | |
271 | ent.removeUpdateTransaction(); |
272 | |
273 | // If we are committing a recovery transaction, remove it from the |
274 | // transaction table. The xact object which is doing the work is |
275 | // not going to be closed even though the transaction is done. |
276 | if (ent.isRecovery()) |
277 | remove(id); |
278 | } |
279 | |
280 | return; |
281 | } |
282 | |
283 | /************************************************************************** |
284 | * Transaction table methods used by XA. |
285 | ************************************************************************** |
286 | */ |
287 | |
288 | /** |
289 | * Return the hash table to the XA layer. |
290 | * <p> |
291 | * The XA code will do linear read-only operations on the hash table, |
292 | * write operations are only done in this module. It is a little ugly |
293 | * to export the hash table, but I wanted to move the XA specific code |
294 | * into the XA module, so that we could configure out the XA code if |
295 | * necessary. |
296 | * <p> |
297 | * |
298 | * Must be MT -safe, depends on sync hash table, and must get |
299 | * synchronized(hash_table) for linear searches. |
300 | * |
301 | * @return The ContextManager of the transaction being searched for. |
302 | * |
303 | **/ |
304 | public Hashtable getTableForXA() |
305 | { |
306 | return(trans); |
307 | } |
308 | |
309 | /** |
310 | Change transaction to prepared. |
311 | |
312 | <P>MT - unsafe, caller is recovery, which is single threaded. |
313 | |
314 | @param id the transaction Id |
315 | */ |
316 | void prepareTransaction(TransactionId id) |
317 | { |
318 | // we need to synchronize on the transaction table because we have to |
319 | // prevent this state change from happening when the transaction table |
320 | // itself is written out to the checkpoint. This is the only |
321 | // protection the TransactionTableEntry has to prevent fields in myxact |
322 | // from changing underneath it while it is being written out. |
323 | |
324 | TransactionTableEntry ent = findTransactionEntry(id); |
325 | |
326 | if (SanityManager.DEBUG) |
327 | { |
328 | SanityManager.ASSERT( |
329 | ent != null, "preparing transaction that is not there"); |
330 | } |
331 | |
332 | ent.prepareTransaction(); |
333 | |
334 | return; |
335 | } |
336 | |
337 | /** |
338 | * Find a transaction in the table by Global transaction id. |
339 | * <p> |
340 | * This routine use to be only called during offline recovery so performance |
341 | * was not critical. Since that time more calls have been made, including |
342 | * one in startGlobalTransaction() so a linear search may no longer |
343 | * be appropriate. See DERBY-828. |
344 | * |
345 | * @return The ContextManager of the transaction being searched for. |
346 | * |
347 | * @param global_id The global transaction we are searching for. |
348 | **/ |
349 | public ContextManager findTransactionContextByGlobalId( |
350 | GlobalXactId global_id) |
351 | { |
352 | ContextManager cm = null; |
353 | |
354 | // Need to hold sync while linear searching the hash table. |
355 | synchronized (trans) |
356 | { |
357 | for (Enumeration e = trans.elements(); e.hasMoreElements();) |
358 | { |
359 | TransactionTableEntry entry = |
360 | (TransactionTableEntry) e.nextElement(); |
361 | |
362 | if (entry != null) |
363 | { |
364 | GlobalTransactionId entry_gid = entry.getGid(); |
365 | |
366 | if (entry_gid != null && entry_gid.equals(global_id)) |
367 | { |
368 | cm = entry.getXact().getContextManager(); |
369 | break; |
370 | } |
371 | } |
372 | } |
373 | } |
374 | |
375 | return(cm); |
376 | } |
377 | |
378 | |
379 | /*********************************************************** |
380 | * called when system is being quiesced, must be MT - safe |
381 | ***********************************************************/ |
382 | /** |
383 | Return true if there is no transaction actively updating the database. |
384 | New transaction may be started or old transaction committed |
385 | right afterward, the caller of this routine must have other ways to |
386 | stop transactions from starting or ending. |
387 | |
388 | <P>MT - safe |
389 | */ |
390 | boolean hasActiveUpdateTransaction() |
391 | { |
392 | synchronized (this) |
393 | { |
394 | for (Enumeration e = trans.elements(); e.hasMoreElements(); ) |
395 | { |
396 | TransactionTableEntry ent = (TransactionTableEntry)e.nextElement(); |
397 | if (ent != null && ent.isUpdate()) |
398 | return true; |
399 | } |
400 | } |
401 | return false; |
402 | } |
403 | |
404 | |
405 | |
406 | /************************************************************ |
407 | * methods called only by checkpoint |
408 | ***********************************************************/ |
409 | /* |
410 | * Formatable methods |
411 | */ |
412 | |
413 | /** |
414 | Return my format identifier. |
415 | */ |
416 | public int getTypeFormatId() { |
417 | return StoredFormatIds.RAW_STORE_TRANSACTION_TABLE; |
418 | } |
419 | |
420 | /** |
421 | @exception IOException problem reading the transaction table |
422 | */ |
423 | public void writeExternal(ObjectOutput out) throws IOException |
424 | { |
425 | //don't let the transactions status change while writing out(beetle:5533) |
426 | //Note: syncing both on trans and this variable could be avoided if |
427 | //all the routines in this class are sycned on "this" and does not |
428 | //depend on hash table synchronization. But that will be overkill |
429 | //because this routine gets called only on checkpoints and others |
430 | //are used more often. |
431 | |
432 | synchronized(this) |
433 | { |
434 | // don't touch the transaction table when I am being written out |
435 | synchronized(trans) |
436 | { |
437 | int count = 0; |
438 | int maxcount = trans.size(); |
439 | |
440 | // first count up the number of active update transactions |
441 | for (Enumeration e = trans.elements(); |
442 | e.hasMoreElements(); ) |
443 | { |
444 | TransactionTableEntry ent = (TransactionTableEntry)e.nextElement(); |
445 | if (ent != null && ent.isUpdate()) |
446 | count++; |
447 | } |
448 | |
449 | CompressedNumber.writeInt(out, count); |
450 | |
451 | // now write them out |
452 | if (count > 0) |
453 | { |
454 | for (Enumeration e = trans.elements(); |
455 | e.hasMoreElements() ; ) |
456 | { |
457 | TransactionTableEntry ent = (TransactionTableEntry)e.nextElement(); |
458 | if (ent != null && ent.isUpdate()) |
459 | { |
460 | // only writes out update transaction |
461 | out.writeObject(ent); |
462 | } |
463 | } |
464 | } |
465 | } |
466 | } |
467 | } |
468 | |
469 | /************************************************************ |
470 | * methods called only by recovery |
471 | ************************************************************/ |
472 | |
473 | |
474 | /** |
475 | @exception IOException problem reading the transaction table |
476 | @exception ClassNotFoundException problem reading the transaction table |
477 | */ |
478 | public void readExternal(ObjectInput in) |
479 | throws IOException, ClassNotFoundException |
480 | { |
481 | // RESOLVE: this is only read in checkpoint record, but we have not |
482 | // finish the work on using this transaction table to cut down on redo |
483 | // so this transaction table is effectively and futilely thrown away! |
484 | |
485 | int count = CompressedNumber.readInt(in); |
486 | if (count == 0) |
487 | return; |
488 | |
489 | for (int i = 0; i < count; i++) |
490 | { |
491 | TransactionTableEntry ent = |
492 | (TransactionTableEntry)in.readObject(); |
493 | |
494 | if (SanityManager.DEBUG) |
495 | SanityManager.ASSERT( |
496 | ent.getXid() != null, |
497 | "read in transaction table entry with null id"); |
498 | |
499 | trans.put(ent.getXid(), ent); |
500 | |
501 | if (ent.isUpdate() && |
502 | XactId.compare(ent.getXid(), largestUpdateXactId) > 0) |
503 | { |
504 | largestUpdateXactId = ent.getXid(); |
505 | } |
506 | } |
507 | |
508 | |
509 | } |
510 | |
511 | /** |
512 | Return the largest update transactionId I have seen so far. |
513 | |
514 | <P>MT - unsafe, caller is recovery, which is single threaded. |
515 | */ |
516 | public TransactionId largestUpdateXactId() |
517 | { |
518 | return largestUpdateXactId; |
519 | } |
520 | |
521 | |
522 | /** |
523 | Is there an active internal transaction in the transaction table. |
524 | |
525 | <P>MT - unsafe, caller is recovery, which is single threaded. |
526 | */ |
527 | public boolean hasRollbackFirstTransaction() |
528 | { |
529 | for (Enumeration e = trans.elements(); |
530 | e.hasMoreElements() ; ) |
531 | { |
532 | TransactionTableEntry ent = (TransactionTableEntry)e.nextElement(); |
533 | |
534 | if (ent != null && ent.isRecovery() && |
535 | (ent.getTransactionStatus() & |
536 | Xact.RECOVERY_ROLLBACK_FIRST) != 0) |
537 | { |
538 | return true; |
539 | } |
540 | } |
541 | return false; |
542 | } |
543 | |
544 | /** |
545 | Is there a prepared transaction in the transaction table. |
546 | |
547 | <P>MT - unsafe, caller is recovery, which is single threaded. |
548 | */ |
549 | public boolean hasPreparedRecoveredXact() |
550 | { |
551 | for (Enumeration e = trans.elements(); e.hasMoreElements(); ) |
552 | { |
553 | TransactionTableEntry ent = (TransactionTableEntry) e.nextElement(); |
554 | |
555 | if (ent != null && ent.isRecovery() && |
556 | (ent.getTransactionStatus() & Xact.END_PREPARED) != 0) |
557 | { |
558 | return true; |
559 | } |
560 | } |
561 | return false; |
562 | } |
563 | |
564 | |
565 | /** |
566 | Get the most recently added transaction that says it needs to be |
567 | rolled back first (an InternalXact) from the transaction table and make |
568 | the passed in transaction assume its identity. |
569 | <B> Should only be used in recovery undo !! </B> |
570 | RESOLVE: (sku)I don't think even these internal transactions need to be |
571 | rolled back in the reverse order, because they are physical in nature. |
572 | But it won't hurt. |
573 | |
574 | <P>MT - unsafe, caller is recovery, which is single threaded. |
575 | */ |
576 | public boolean getMostRecentRollbackFirstTransaction(RawTransaction tran) |
577 | { |
578 | |
579 | if (trans.isEmpty()) |
580 | { |
581 | // set tranaction to idle |
582 | return findAndAssumeTransaction((TransactionId)null, tran); |
583 | } |
584 | |
585 | TransactionId id = null; |
586 | for (Enumeration e = trans.elements(); |
587 | e.hasMoreElements() ; ) |
588 | { |
589 | TransactionTableEntry ent = (TransactionTableEntry)e.nextElement(); |
590 | |
591 | if (ent != null && ent.isUpdate() && ent.isRecovery() && |
592 | (ent.getTransactionStatus() & Xact.RECOVERY_ROLLBACK_FIRST) != 0) |
593 | { |
594 | // try to locate the most recent one |
595 | if (id == null || XactId.compare(id, ent.getXid()) < 0) |
596 | id = ent.getXid(); |
597 | } |
598 | } |
599 | |
600 | if (id == null) // set transaction to idle |
601 | { |
602 | return findAndAssumeTransaction(id, tran); |
603 | } |
604 | else |
605 | { |
606 | // there is a rollback first transaction |
607 | boolean found = |
608 | findAndAssumeTransaction(id, tran); |
609 | |
610 | if (SanityManager.DEBUG) |
611 | { |
612 | if (!found) |
613 | { |
614 | SanityManager.THROWASSERT( |
615 | "cannot find transaction " + id + " in table"); |
616 | } |
617 | } |
618 | |
619 | return true; |
620 | } |
621 | } |
622 | |
623 | /** |
624 | Get the most recently non-prepared added transaction from the |
625 | transaction table and make the passed in transaction assume its |
626 | identity. Prepared transactions will not be undone. |
627 | |
628 | RESOLVE: (sku) I don't think normal user transactions needs to be |
629 | rolled back in order, but it won't hurt. |
630 | |
631 | <B> Should only be used in recovery undo !! </B> |
632 | |
633 | <P>MT - unsafe, caller is recovery, which is single threaded. |
634 | */ |
635 | public boolean getMostRecentTransactionForRollback(RawTransaction tran) |
636 | { |
637 | TransactionId id = null; |
638 | |
639 | if (!trans.isEmpty()) |
640 | { |
641 | for (Enumeration e = trans.elements(); |
642 | e.hasMoreElements() ; ) |
643 | { |
644 | TransactionTableEntry ent = |
645 | (TransactionTableEntry)e.nextElement(); |
646 | |
647 | if (ent != null && |
648 | ent.isUpdate() && |
649 | ent.isRecovery() && |
650 | !ent.isPrepared()) |
651 | { |
652 | // try to locate the most recent one |
653 | if (id == null || XactId.compare(id, ent.getXid()) < 0) |
654 | id = ent.getXid(); |
655 | } |
656 | |
657 | if (SanityManager.DEBUG) |
658 | { |
659 | if (ent != null && |
660 | ent.isUpdate() && |
661 | ent.isRecovery() && |
662 | (ent.getTransactionStatus() & |
663 | Xact.RECOVERY_ROLLBACK_FIRST) != 0) |
664 | { |
665 | SanityManager.THROWASSERT( |
666 | "still rollback first xacts in the tran table!"); |
667 | } |
668 | } |
669 | } |
670 | |
671 | if (SanityManager.DEBUG) |
672 | { |
673 | // if all transactions are prepared then it is possible that |
674 | // no transaction will be found, in that case id will be null. |
675 | if (id != null) |
676 | { |
677 | SanityManager.ASSERT(findTransactionEntry(id) != null); |
678 | } |
679 | else |
680 | { |
681 | // all transactions in the table must be prepared. |
682 | for (Enumeration e = trans.elements(); e.hasMoreElements();) |
683 | { |
684 | TransactionTableEntry ent = |
685 | (TransactionTableEntry)e.nextElement(); |
686 | SanityManager.ASSERT(ent.isPrepared()); |
687 | } |
688 | } |
689 | } |
690 | } |
691 | |
692 | return(findAndAssumeTransaction(id, tran)); |
693 | } |
694 | |
695 | /** |
696 | Get the most recently added transaction that says it is prepared during |
697 | recovery the transaction table and make the passed in transaction |
698 | assume its identity. This routine turns off the isRecovery() state |
699 | <B> Should only be used in recovery handle prepare after undo !! </B> |
700 | |
701 | <P>MT - unsafe, caller is recovery, which is single threaded. |
702 | */ |
703 | |
704 | /** |
705 | * Get the most recent recovered prepared transaction. |
706 | * <p> |
707 | * Get the most recently added transaction that says it is prepared during |
708 | * recovery the transaction table and make the passed in transaction |
709 | * assume its identity. |
710 | * <p> |
711 | * This routine, unlike the redo and rollback getMostRecent*() routines |
712 | * expects a brand new transaction to be passed in. If a candidate |
713 | * transaction is found, then upon return the transaction table will |
714 | * be altered such that the old entry no longer exists, and a new entry |
715 | * will exist pointing to the transaction passed in. The new entry will |
716 | * look the same as if the prepared transaction had been created during |
717 | * runtime rather than recovery. |
718 | * |
719 | * <B> Should only be used in recovery handle prepare after undo !! </B> |
720 | * |
721 | * <P>MT - unsafe, caller is recovery, which is single threaded. |
722 | * |
723 | * @return true if a candidate transaction has been found. false if no |
724 | * prepared/recovery transactions found in the table. |
725 | * |
726 | * @param tran Newly allocated transaction to add to link to a entry. |
727 | * |
728 | **/ |
729 | public boolean getMostRecentPreparedRecoveredXact( |
730 | RawTransaction tran) |
731 | { |
732 | TransactionTableEntry found_ent = null; |
733 | |
734 | if (!trans.isEmpty()) |
735 | { |
736 | TransactionId id = null; |
737 | GlobalTransactionId gid = null; |
738 | TransactionTableEntry ent; |
739 | |
740 | for (Enumeration e = trans.elements(); e.hasMoreElements(); ) |
741 | { |
742 | ent = (TransactionTableEntry)e.nextElement(); |
743 | |
744 | if (ent != null && |
745 | ent.isRecovery() && |
746 | ent.isPrepared()) |
747 | { |
748 | // try to locate the most recent one |
749 | if (id == null || XactId.compare(id, ent.getXid()) < 0) |
750 | { |
751 | found_ent = ent; |
752 | id = ent.getXid(); |
753 | gid = ent.getGid(); |
754 | } |
755 | } |
756 | } |
757 | |
758 | if (SanityManager.DEBUG) |
759 | { |
760 | if (found_ent == null) |
761 | { |
762 | // if no entry's were found then the transaction table |
763 | // should have the passed in idle tran, and the rest should |
764 | // be non-recover, prepared global transactions. |
765 | for (Enumeration e = trans.elements(); e.hasMoreElements();) |
766 | { |
767 | ent = (TransactionTableEntry)e.nextElement(); |
768 | |
769 | if (XactId.compare(ent.getXid(), tran.getId()) != 0) |
770 | { |
771 | SanityManager.ASSERT( |
772 | !ent.isRecovery() && ent.isPrepared()); |
773 | SanityManager.ASSERT(ent.getGid() != null); |
774 | } |
775 | } |
776 | } |
777 | } |
778 | |
779 | if (found_ent != null) |
780 | { |
781 | // At this point there are 2 tt entries of interest: |
782 | // new_ent - the read only transaction entry that was |
783 | // created when we allocated a new transaction. |
784 | // We will just throw this one away after |
785 | // assuming the identity of the global xact. |
786 | // found_ent |
787 | // - the entry of the transaction that we are going |
788 | // to take over. |
789 | TransactionTableEntry new_ent = |
790 | (TransactionTableEntry) trans.remove(tran.getId()); |
791 | |
792 | // At this point only the found_ent should be in the table. |
793 | if (SanityManager.DEBUG) |
794 | { |
795 | SanityManager.ASSERT(findTransactionEntry(id) == found_ent); |
796 | } |
797 | |
798 | ((Xact) tran).assumeGlobalXactIdentity(found_ent); |
799 | |
800 | // transform this recovery entry, into a runtime entry. |
801 | found_ent.unsetRecoveryStatus(); |
802 | } |
803 | } |
804 | |
805 | return(found_ent != null); |
806 | } |
807 | |
808 | /** |
809 | Get the least recently added (oldest) transaction |
810 | @return the RawTransaction's first log instant |
811 | |
812 | <P>MT - safe, caller can be recovery or checkpoint |
813 | */ |
814 | public LogInstant getFirstLogInstant() |
815 | { |
816 | // assume for now that it is acceptable to return null if a transaction |
817 | // starts right in the middle of this call. |
818 | |
819 | if (trans.isEmpty()) |
820 | { |
821 | return null; |
822 | } |
823 | else |
824 | { |
825 | LogInstant logInstant = null; |
826 | |
827 | // bug 5632: need to sychronize so that another thread does not |
828 | // come in and disrupt the for loop, we got an exception on next, |
829 | // likely because hash table changed by another thread after |
830 | // hasMoreElements() called, but before nextElement(). |
831 | |
832 | synchronized (trans) |
833 | { |
834 | for (Enumeration e = trans.elements(); e.hasMoreElements(); ) |
835 | { |
836 | TransactionTableEntry ent = |
837 | (TransactionTableEntry)e.nextElement(); |
838 | |
839 | if (ent != null && ent.isUpdate()) |
840 | { |
841 | if (logInstant == null || |
842 | ent.getFirstLog().lessThan(logInstant)) |
843 | { |
844 | logInstant = ent.getFirstLog(); |
845 | } |
846 | } |
847 | } |
848 | } |
849 | |
850 | return logInstant; |
851 | } |
852 | } |
853 | |
854 | /** |
855 | Find a transaction using the transaction id, and make the passed in |
856 | transaction assume the identity and properties of that transaction. |
857 | |
858 | <P>MT - unsafe, caller is recovery, which is single threaded. |
859 | |
860 | @param id transaction Id |
861 | @param tran the transaction that was made to assume the transactionID |
862 | and all other relavent information stored in the transaction table |
863 | @return true if transaction can be found, false otherwise |
864 | */ |
865 | boolean findAndAssumeTransaction( |
866 | TransactionId id, |
867 | RawTransaction tran) |
868 | { |
869 | // the only caller for this method right now is recovery. |
870 | // No need to put in any concurrency control |
871 | TransactionTableEntry ent = null; |
872 | |
873 | if (id != null && !trans.isEmpty()) |
874 | { |
875 | ent = findTransactionEntry(id); |
876 | |
877 | if (SanityManager.DEBUG) |
878 | { |
879 | if (ent != null) |
880 | SanityManager.ASSERT(ent.isRecovery(), |
881 | "assuming the id of a non-recovery transaction"); |
882 | } |
883 | } |
884 | |
885 | // if no transaction entry found, set transaction to idle |
886 | ((Xact)tran).assumeIdentity(ent); |
887 | |
888 | return(ent != null); |
889 | |
890 | } |
891 | |
892 | /********************************************************** |
893 | * Transaction table vti and diagnostics |
894 | * MT - unsafe, caller is getting a snap shot which may be inconsistent |
895 | *********************************************************/ |
896 | |
897 | /** |
898 | Get a printable version of the transaction table |
899 | */ |
900 | public TransactionInfo[] getTransactionInfo() |
901 | { |
902 | if (trans.isEmpty()) |
903 | return null; |
904 | |
905 | // while taking a snap shot, no adding or removing of transaction |
906 | TransactionInfo[] tinfo; |
907 | |
908 | if (SanityManager.DEBUG) |
909 | SanityManager.DEBUG("TranTrace", toString()); |
910 | |
911 | synchronized(this) |
912 | { |
913 | int ntran = trans.size(); |
914 | tinfo = new TransactionTableEntry[ntran]; |
915 | |
916 | LogInstant logInstant = null; |
917 | int i = 0; |
918 | |
919 | for (Enumeration e = trans.elements(); |
920 | e.hasMoreElements(); ) |
921 | { |
922 | TransactionTableEntry ent = |
923 | (TransactionTableEntry)e.nextElement(); |
924 | |
925 | if (ent != null) |
926 | tinfo[i++] = (TransactionTableEntry)ent.clone(); |
927 | |
928 | if (SanityManager.DEBUG) |
929 | SanityManager.ASSERT(ent != null, "transaction table has null entry"); |
930 | } |
931 | } |
932 | |
933 | return tinfo; |
934 | } |
935 | |
936 | public String toString() |
937 | { |
938 | if (SanityManager.DEBUG) |
939 | { |
940 | StringBuffer str = new StringBuffer(1000). |
941 | append("\n**************************\n"). |
942 | append(super.toString()). |
943 | append("\nTransaction Table: size = ").append(trans.size()). |
944 | append(" largestUpdateXactId = ").append(largestUpdateXactId). |
945 | append("\n"); |
946 | |
947 | boolean hasReadOnlyTransaction = false; |
948 | |
949 | for (Enumeration e = trans.elements(); |
950 | e.hasMoreElements(); ) |
951 | { |
952 | TransactionTableEntry ent = |
953 | (TransactionTableEntry)e.nextElement(); |
954 | |
955 | if (ent != null && ent.isUpdate()) |
956 | str.append(ent.toString()); |
957 | |
958 | if (ent != null && !ent.isUpdate()) |
959 | hasReadOnlyTransaction = true; |
960 | } |
961 | |
962 | if (hasReadOnlyTransaction) |
963 | { |
964 | str.append("\n READ ONLY TRANSACTIONS \n"); |
965 | |
966 | for (Enumeration e = trans.elements(); |
967 | e.hasMoreElements(); ) |
968 | { |
969 | TransactionTableEntry ent = |
970 | (TransactionTableEntry)e.nextElement(); |
971 | |
972 | if (ent != null && !ent.isUpdate()) |
973 | str.append(ent.toString()); |
974 | } |
975 | } |
976 | str.append("---------------------------"); |
977 | return str.toString(); |
978 | } |
979 | else |
980 | return null; |
981 | } |
982 | |
983 | |
984 | } |
985 | |