001: /*
002: * Copyright (c) 2003-2006 Terracotta, Inc. All rights reserved.
003: */
004: package com.tc.objectserver.tx;
005:
006: import com.tc.exception.ImplementMe;
007: import com.tc.net.groups.ClientID;
008: import com.tc.net.protocol.tcm.ChannelID;
009: import com.tc.object.ObjectID;
010: import com.tc.object.dmi.DmiDescriptor;
011: import com.tc.object.dna.impl.ObjectStringSerializer;
012: import com.tc.object.gtx.GlobalTransactionID;
013: import com.tc.object.gtx.GlobalTransactionIDGenerator;
014: import com.tc.object.lockmanager.api.LockID;
015: import com.tc.object.tx.ServerTransactionID;
016: import com.tc.object.tx.TransactionID;
017: import com.tc.object.tx.TxnBatchID;
018: import com.tc.object.tx.TxnType;
019: import com.tc.objectserver.core.api.TestDNA;
020: import com.tc.test.TCTestCase;
021: import com.tc.util.Assert;
022: import com.tc.util.SequenceID;
023:
024: import java.security.SecureRandom;
025: import java.util.ArrayList;
026: import java.util.Arrays;
027: import java.util.Collections;
028: import java.util.HashMap;
029: import java.util.HashSet;
030: import java.util.Iterator;
031: import java.util.LinkedList;
032: import java.util.List;
033: import java.util.Map;
034: import java.util.Random;
035: import java.util.Set;
036:
037: public class TransactionSequencerTest extends TCTestCase {
038:
039: private int sqID;
040: private int txnID;
041: private int batchID;
042: private ClientID clientID;
043: private TransactionSequencer sequencer;
044: private int start;
045: private GlobalTransactionIDGenerator gidGenerator;
046:
047: public void setUp() throws Exception {
048: txnID = 100;
049: sqID = 100;
050: batchID = 100;
051: start = 1;
052: clientID = new ClientID(new ChannelID(0));
053: sequencer = new TransactionSequencer();
054: gidGenerator = new TestGlobalTransactionIDGenerator();
055: }
056:
057: // Test 1
058: // Nothing is pending - disjoint anyway
059: public void testNoPendingDisjointTxn() throws Exception {
060: List txns = createDisjointTxns(5);
061: sequencer.addTransactions(txns);
062: assertEquals(txns, getAllTxnsPossible());
063: assertFalse(sequencer.isPending(txns));
064: }
065:
066: // Test 2
067: // Nothing is pending - not disjoint though
068: public void testNoPendingJointTxn() throws Exception {
069: List txns = createIntersectingLocksTxns(5);
070: sequencer.addTransactions(txns);
071: assertEquals(txns, getAllTxnsPossible());
072: assertFalse(sequencer.isPending(txns));
073: }
074:
075: // Test 3
076: // txn goes pending - disjoint anyway
077: public void testPendingDisJointTxn() throws Exception {
078: List txns = createDisjointTxns(5);
079: sequencer.addTransactions(txns);
080: ServerTransaction t1 = sequencer.getNextTxnToProcess();
081: assertNotNull(t1);
082: // Make it pending
083: sequencer.makePending(t1);
084: assertTrue(sequencer.isPending(txns));
085: txns.remove(t1);
086: assertEquals(txns, getAllTxnsPossible());
087: assertTrue(sequencer.isPending(Arrays
088: .asList(new Object[] { t1 })));
089: // Nomore txns
090: assertNull(sequencer.getNextTxnToProcess());
091: sequencer.makeUnpending(t1);
092: assertFalse(sequencer.isPending(Arrays
093: .asList(new Object[] { t1 })));
094: // Nomore txns
095: assertNull(sequencer.getNextTxnToProcess());
096: }
097:
098: // Test 4
099: // txn goes pending - intersecting set
100: public void testPendingJointAtLocksTxn() throws Exception {
101: List txns = createIntersectingLocksTxns(5);
102: sequencer.addTransactions(txns);
103: ServerTransaction t1 = sequencer.getNextTxnToProcess();
104: assertNotNull(t1);
105: // Make it pending
106: sequencer.makePending(t1);
107: assertTrue(sequencer.isPending(txns));
108: txns.remove(t1);
109:
110: // Since locks are common no txn should be available
111: assertNull(sequencer.getNextTxnToProcess());
112: assertTrue(sequencer.isPending(Arrays
113: .asList(new Object[] { t1 })));
114: sequencer.makeUnpending(t1);
115: assertFalse(sequencer.isPending(Arrays
116: .asList(new Object[] { t1 })));
117: // Rest of the txns
118: assertEquals(txns, getAllTxnsPossible());
119: }
120:
121: // Test 5
122: // txn goes pending - intersecting set
123: public void testPendingJointAtObjectsTxn() throws Exception {
124: List txns = createIntersectingObjectsTxns(5);
125: sequencer.addTransactions(txns);
126: ServerTransaction t1 = sequencer.getNextTxnToProcess();
127: assertNotNull(t1);
128: // Make it pending
129: sequencer.makePending(t1);
130: assertTrue(sequencer.isPending(txns));
131: txns.remove(t1);
132:
133: // Since locks are common no txn should be available
134: assertNull(sequencer.getNextTxnToProcess());
135: assertTrue(sequencer.isPending(Arrays
136: .asList(new Object[] { t1 })));
137: sequencer.makeUnpending(t1);
138: assertFalse(sequencer.isPending(Arrays
139: .asList(new Object[] { t1 })));
140: // Rest of the txns
141: assertEquals(txns, getAllTxnsPossible());
142: }
143:
144: // Test 6
145: // txn goes pending - intersecting set
146: public void testPendingJointAtBothLocksAndObjectsTxn()
147: throws Exception {
148: List txns = createIntersectingLocksObjectsTxns(5);
149: sequencer.addTransactions(txns);
150: ServerTransaction t1 = sequencer.getNextTxnToProcess();
151: assertNotNull(t1);
152: // Make it pending
153: sequencer.makePending(t1);
154: assertTrue(sequencer.isPending(txns));
155: txns.remove(t1);
156:
157: // Since locks are common no txn should be available
158: assertNull(sequencer.getNextTxnToProcess());
159: assertTrue(sequencer.isPending(Arrays
160: .asList(new Object[] { t1 })));
161: sequencer.makeUnpending(t1);
162: assertFalse(sequencer.isPending(Arrays
163: .asList(new Object[] { t1 })));
164: // Rest of the txns
165: assertEquals(txns, getAllTxnsPossible());
166: }
167:
168: // Test 7
169: // Test error conditions
170: public void testErrorConditions() throws Exception {
171: // Call makepending twice
172: List txns = createDisjointTxns(5);
173: sequencer.addTransactions(txns);
174: ServerTransaction t1 = sequencer.getNextTxnToProcess();
175: assertNotNull(t1);
176: sequencer.makePending(t1);
177: assertTrue(sequencer.isPending(txns));
178: try {
179: sequencer.makePending(t1);
180: fail();
181: } catch (Throwable t) {
182: // expected
183: }
184:
185: // Call make unpending for something that is not pendin
186: ServerTransaction t2 = sequencer.getNextTxnToProcess();
187: assertNotNull(t2);
188: try {
189: sequencer.makeUnpending(t2);
190: fail();
191: } catch (Throwable t) {
192: // expected
193: }
194: sequencer.makeUnpending(t1);
195: }
196:
197: public void testOrderingByOID() {
198: List txns = new ArrayList();
199:
200: int lock = 0;
201:
202: ServerTransaction txn1 = new ServerTransactionImpl(
203: gidGenerator, new TxnBatchID(batchID),
204: new TransactionID(1), new SequenceID(sqID++),
205: createLocks(lock, lock++), clientID, createDNAs(1, 1),
206: new ObjectStringSerializer(), Collections.EMPTY_MAP,
207: TxnType.NORMAL, new LinkedList(),
208: DmiDescriptor.EMPTY_ARRAY);
209:
210: ServerTransaction txn2 = new ServerTransactionImpl(
211: gidGenerator, new TxnBatchID(batchID),
212: new TransactionID(2), new SequenceID(sqID++),
213: createLocks(lock, lock++), clientID, createDNAs(2, 2),
214: new ObjectStringSerializer(), Collections.EMPTY_MAP,
215: TxnType.NORMAL, new LinkedList(),
216: DmiDescriptor.EMPTY_ARRAY);
217:
218: ServerTransaction txn3 = new ServerTransactionImpl(
219: gidGenerator, new TxnBatchID(batchID),
220: new TransactionID(3), new SequenceID(sqID++),
221: createLocks(lock, lock++), clientID, createDNAs(2, 3),
222: new ObjectStringSerializer(), Collections.EMPTY_MAP,
223: TxnType.NORMAL, new LinkedList(),
224: DmiDescriptor.EMPTY_ARRAY);
225:
226: ServerTransaction txn4 = new ServerTransactionImpl(
227: gidGenerator, new TxnBatchID(batchID),
228: new TransactionID(4), new SequenceID(sqID++),
229: createLocks(lock, lock++), clientID, createDNAs(1, 2),
230: new ObjectStringSerializer(), Collections.EMPTY_MAP,
231: TxnType.NORMAL, new LinkedList(),
232: DmiDescriptor.EMPTY_ARRAY);
233:
234: txns.add(txn1);
235: txns.add(txn2);
236: txns.add(txn3);
237: txns.add(txn4);
238: sequencer.addTransactions(txns);
239:
240: sequencer.makePending(sequencer.getNextTxnToProcess());
241: sequencer.makePending(sequencer.getNextTxnToProcess());
242:
243: Object o;
244: o = sequencer.getNextTxnToProcess();
245: Assert.assertNull(o);
246: o = sequencer.getNextTxnToProcess();
247: Assert.assertNull(o);
248:
249: sequencer.makeUnpending(txn2);
250: sequencer.makeUnpending(txn1);
251:
252: ServerTransaction shouldBe3 = sequencer.getNextTxnToProcess();
253: ServerTransaction shouldBe4 = sequencer.getNextTxnToProcess();
254:
255: Assert.assertEquals(txn3, shouldBe3);
256: Assert.assertEquals(txn4, shouldBe4);
257: }
258:
259: public void testOrderingByLock() {
260: List txns = new ArrayList();
261:
262: int oid = 0;
263:
264: ServerTransaction txn1 = new ServerTransactionImpl(
265: gidGenerator, new TxnBatchID(batchID),
266: new TransactionID(1), new SequenceID(sqID++),
267: createLocks(1, 1), clientID, createDNAs(oid, oid++),
268: new ObjectStringSerializer(), Collections.EMPTY_MAP,
269: TxnType.NORMAL, new LinkedList(),
270: DmiDescriptor.EMPTY_ARRAY);
271:
272: ServerTransaction txn2 = new ServerTransactionImpl(
273: gidGenerator, new TxnBatchID(batchID),
274: new TransactionID(2), new SequenceID(sqID++),
275: createLocks(2, 2), clientID, createDNAs(oid, oid++),
276: new ObjectStringSerializer(), Collections.EMPTY_MAP,
277: TxnType.NORMAL, new LinkedList(),
278: DmiDescriptor.EMPTY_ARRAY);
279:
280: ServerTransaction txn3 = new ServerTransactionImpl(
281: gidGenerator, new TxnBatchID(batchID),
282: new TransactionID(3), new SequenceID(sqID++),
283: createLocks(2, 3), clientID, createDNAs(oid, oid++),
284: new ObjectStringSerializer(), Collections.EMPTY_MAP,
285: TxnType.NORMAL, new LinkedList(),
286: DmiDescriptor.EMPTY_ARRAY);
287:
288: ServerTransaction txn4 = new ServerTransactionImpl(
289: gidGenerator, new TxnBatchID(batchID),
290: new TransactionID(4), new SequenceID(sqID++),
291: createLocks(1, 2), clientID, createDNAs(oid, oid++),
292: new ObjectStringSerializer(), Collections.EMPTY_MAP,
293: TxnType.NORMAL, new LinkedList(),
294: DmiDescriptor.EMPTY_ARRAY);
295:
296: txns.add(txn1);
297: txns.add(txn2);
298: txns.add(txn3);
299: txns.add(txn4);
300: sequencer.addTransactions(txns);
301:
302: sequencer.makePending(sequencer.getNextTxnToProcess());
303: sequencer.makePending(sequencer.getNextTxnToProcess());
304:
305: Object o;
306: o = sequencer.getNextTxnToProcess();
307: Assert.assertNull(o);
308: o = sequencer.getNextTxnToProcess();
309: Assert.assertNull(o);
310:
311: sequencer.makeUnpending(txn2);
312: sequencer.makeUnpending(txn1);
313:
314: ServerTransaction shouldBe3 = sequencer.getNextTxnToProcess();
315: ServerTransaction shouldBe4 = sequencer.getNextTxnToProcess();
316:
317: Assert.assertEquals(txn3, shouldBe3);
318: Assert.assertEquals(txn4, shouldBe4);
319: }
320:
321: public void testRandom() {
322: for (int i = 0; i < 100; i++) {
323: System.err.println("Running testRandom : " + i);
324: doRandom();
325: sequencer = new TransactionSequencer();
326: }
327: }
328:
329: public void testRandomFailedSeed1() {
330: long seed = -7748167846395034562L;
331: System.err.println("Testing failed seed : " + seed);
332: doRandom(seed);
333: }
334:
335: public void testRandomFailedSeed2() {
336: long seed = -149113776740941224L;
337: System.err.println("Testing failed seed : " + seed);
338: doRandom(seed);
339: }
340:
341: // XXX: multi-threaded version of this?
342: // XXX: add cases with locks in common between TXNs
343: private void doRandom() {
344: final long seed = new SecureRandom().nextLong();
345: System.err.println("seed is " + seed);
346: doRandom(seed);
347: }
348:
349: private void doRandom(long seed) {
350: Random rnd = new Random(seed);
351:
352: int lock = 0;
353: final int numObjects = 25;
354: long versionsIn[] = new long[numObjects];
355: long versionsRecv[] = new long[numObjects];
356:
357: Set pending = new HashSet();
358:
359: for (int loop = 0; loop < 5000; loop++) {
360: List txns = new ArrayList();
361: for (int i = 0, n = rnd.nextInt(3) + 1; i < n; i++) {
362: txns.add(createRandomTxn(rnd.nextInt(3) + 1,
363: versionsIn, rnd, lock++));
364: }
365:
366: sequencer.addTransactions(txns);
367:
368: ServerTransaction next = null;
369: while ((next = sequencer.getNextTxnToProcess()) != null) {
370: if (rnd.nextInt(3) == 0) {
371: sequencer.makePending(next);
372: pending.add(next);
373: continue;
374: }
375:
376: processTransaction(next, versionsRecv);
377:
378: if (pending.size() > 0 && rnd.nextInt(4) == 0) {
379: for (int i = 0, n = rnd.nextInt(pending.size()); i < n; i++) {
380: Iterator iter = pending.iterator();
381: ServerTransaction pendingTxn = (ServerTransaction) iter
382: .next();
383: iter.remove();
384: processTransaction(pendingTxn, versionsRecv);
385: sequencer.makeUnpending(pendingTxn);
386: }
387: }
388: }
389:
390: }
391: }
392:
393: private void processTransaction(ServerTransaction next,
394: long[] versionsRecv) {
395: for (Iterator iter = next.getChanges().iterator(); iter
396: .hasNext();) {
397: TestDNA dna = (TestDNA) iter.next();
398: int oid = (int) dna.getObjectID().toLong();
399: long ver = dna.version;
400: long expect = versionsRecv[oid] + 1;
401: if (expect != ver) {
402: //
403: throw new AssertionError(oid
404: + " : Expected change to increment to version "
405: + expect + ", but change was to version " + ver);
406: }
407: versionsRecv[oid] = ver;
408: }
409: }
410:
411: private ServerTransaction createRandomTxn(int numObjects,
412: long[] versions, Random rnd, int lockID) {
413: Map dnas = new HashMap();
414: while (numObjects > 0) {
415: int i = rnd.nextInt(versions.length);
416: if (!dnas.containsKey(new Integer(i))) {
417: TestDNA dna = new TestDNA(new ObjectID(i));
418: dna.version = ++versions[i];
419: dnas.put(new Integer(i), dna);
420: numObjects--;
421: }
422: }
423:
424: return new ServerTransactionImpl(gidGenerator, new TxnBatchID(
425: batchID), new TransactionID(txnID++), new SequenceID(
426: sqID++), createLocks(lockID, lockID), clientID,
427: new ArrayList(dnas.values()),
428: new ObjectStringSerializer(), Collections.EMPTY_MAP,
429: TxnType.NORMAL, new LinkedList(),
430: DmiDescriptor.EMPTY_ARRAY);
431: }
432:
433: private List getAllTxnsPossible() {
434: List txns = new ArrayList();
435: ServerTransaction txn;
436: while ((txn = sequencer.getNextTxnToProcess()) != null) {
437: txns.add(txn);
438: }
439: return txns;
440: }
441:
442: private List createDisjointTxns(int count) {
443: List txns = new ArrayList();
444: batchID++;
445: int j = 3;
446: while (count-- > 0) {
447: int e = start + j;
448: txns.add(new ServerTransactionImpl(gidGenerator,
449: new TxnBatchID(batchID),
450: new TransactionID(txnID++), new SequenceID(sqID++),
451: createLocks(start, e), clientID, createDNAs(start,
452: e), new ObjectStringSerializer(),
453: Collections.EMPTY_MAP, TxnType.NORMAL,
454: new LinkedList(), DmiDescriptor.EMPTY_ARRAY));
455: start = e + 1;
456: }
457: return txns;
458: }
459:
460: private List createIntersectingLocksTxns(int count) {
461: List txns = new ArrayList();
462: batchID++;
463: int j = 3;
464: while (count-- > 0) {
465: int e = start + j;
466: txns.add(new ServerTransactionImpl(gidGenerator,
467: new TxnBatchID(batchID),
468: new TransactionID(txnID++), new SequenceID(sqID++),
469: createLocks(start, e + j), clientID, createDNAs(
470: start, e), new ObjectStringSerializer(),
471: Collections.EMPTY_MAP, TxnType.NORMAL,
472: new LinkedList(), DmiDescriptor.EMPTY_ARRAY));
473: start = e + 1;
474: }
475: return txns;
476: }
477:
478: private List createIntersectingObjectsTxns(int count) {
479: List txns = new ArrayList();
480: batchID++;
481: int j = 3;
482: while (count-- > 0) {
483: int e = start + j;
484: txns.add(new ServerTransactionImpl(gidGenerator,
485: new TxnBatchID(batchID),
486: new TransactionID(txnID++), new SequenceID(sqID++),
487: createLocks(start, e), clientID, createDNAs(start,
488: e + j), new ObjectStringSerializer(),
489: Collections.EMPTY_MAP, TxnType.NORMAL,
490: new LinkedList(), DmiDescriptor.EMPTY_ARRAY));
491: start = e + 1;
492: }
493: return txns;
494: }
495:
496: private List createIntersectingLocksObjectsTxns(int count) {
497: List txns = new ArrayList();
498: batchID++;
499: int j = 3;
500: while (count-- > 0) {
501: int e = start + j;
502: txns.add(new ServerTransactionImpl(gidGenerator,
503: new TxnBatchID(batchID),
504: new TransactionID(txnID++), new SequenceID(sqID++),
505: createLocks(start, e + j), clientID, createDNAs(
506: start, e + j),
507: new ObjectStringSerializer(),
508: Collections.EMPTY_MAP, TxnType.NORMAL,
509: new LinkedList(), DmiDescriptor.EMPTY_ARRAY));
510: start = e + 1;
511: }
512: return txns;
513: }
514:
515: private List createDNAs(int s, int e) {
516: List dnas = new ArrayList();
517: for (int i = s; i <= e; i++) {
518: dnas.add(new TestDNA(new ObjectID(i)));
519: }
520: return dnas;
521: }
522:
523: private LockID[] createLocks(int s, int e) {
524: LockID[] locks = new LockID[e - s + 1];
525: for (int j = s; j <= e; j++) {
526: locks[j - s] = new LockID("@" + j);
527: }
528: return locks;
529: }
530:
531: private final static class TestGlobalTransactionIDGenerator
532: implements GlobalTransactionIDGenerator {
533:
534: long id = 0;
535:
536: public GlobalTransactionID getOrCreateGlobalTransactionID(
537: ServerTransactionID serverTransactionID) {
538: return new GlobalTransactionID(id++);
539: }
540:
541: public GlobalTransactionID getLowGlobalTransactionIDWatermark() {
542: throw new ImplementMe();
543: }
544:
545: }
546: }
|