1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.transaction.file;
18
19 import java.io.BufferedReader;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.io.OutputStream;
27
28 import javax.transaction.Status;
29
30 import junit.framework.Test;
31 import junit.framework.TestCase;
32 import junit.framework.TestSuite;
33
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37 import org.apache.commons.transaction.util.CommonsLoggingLogger;
38 import org.apache.commons.transaction.util.FileHelper;
39 import org.apache.commons.transaction.util.LoggerFacade;
40 import org.apache.commons.transaction.util.RendezvousBarrier;
41
42
43
44
45
46
47 public class FileResourceManagerTest extends TestCase {
48
49 private static final Log log = LogFactory.getLog(FileResourceManagerTest.class.getName());
50 private static final LoggerFacade sLogger = new CommonsLoggingLogger(log);
51
52 private static final String STORE = "tmp/store";
53 private static final String WORK = "tmp/work";
54 private static final String ENCODING = "ISO-8859-15";
55
56
57
58 private static final long BARRIER_TIMEOUT = 200000;
59
60 private static final String[] INITIAL_FILES = new String[] { STORE + "/olli/Hubert6", STORE + "/olli/Hubert" };
61
62 private static final String STATUS_COMMITTING_CONTEXT =
63 "8\n10\n2000\n1063099404687\n";
64 private static final String[] STATUS_COMMITTING_CONTEXT_CHANGE_FILES =
65 new String[] { "olli/Hubert40", "olli/Hubert50" };
66 private static final String[] STATUS_COMMITTING_CONTEXT_DELETE_FILES = new String[] { "/olli/Hubert" };
67 private static final String[] STATUS_COMMITTING_CONTEXT_RESULT_FILES =
68 new String[] { "Hubert6", "Hubert50", "Hubert40" };
69
70 private static void initCommittingRecovery() throws Throwable {
71 String txId = "COMMITTING";
72 createTxContextFile(txId, STATUS_COMMITTING_CONTEXT);
73 createTxDeleteFiles(txId, STATUS_COMMITTING_CONTEXT_DELETE_FILES);
74 createTxChangeFiles(txId, STATUS_COMMITTING_CONTEXT_CHANGE_FILES);
75 }
76
77 private static final String STATUS_COMMITTED_CONTEXT =
78 "3\n10\n2000\n1063099404687\n";
79 private static final String[] STATUS_COMMITTED_CONTEXT_CHANGE_FILES =
80 new String[] { "olli/Hubert4", "olli/Hubert5" };
81 private static final String[] STATUS_COMMITTED_CONTEXT_DELETE_FILES = new String[] {
82 };
83 private static final String[] STATUS_COMMITTED_CONTEXT_RESULT_FILES = new String[] { "Hubert6", "Hubert" };
84
85 protected static final long TIMEOUT = Long.MAX_VALUE;
86
87 private static int deadlockCnt = 0;
88
89 private static void initCommittedRecovery() throws Throwable {
90 String txId = "COMMITTED";
91 createTxContextFile(txId, STATUS_COMMITTED_CONTEXT);
92 createTxDeleteFiles(txId, STATUS_COMMITTED_CONTEXT_DELETE_FILES);
93 createTxChangeFiles(txId, STATUS_COMMITTED_CONTEXT_CHANGE_FILES);
94 }
95
96 private static final String STATUS_ROLLING_BACK_CONTEXT =
97 "9\n10\n2000\n1063099404687\n";
98 private static final String[] STATUS_ROLLING_BACK_CONTEXT_CHANGE_FILES =
99 new String[] { "olli/Hubert4", "olli/Hubert5" };
100 private static final String[] STATUS_ROLLING_BACK_CONTEXT_DELETE_FILES = new String[] {
101 };
102 private static final String[] STATUS_ROLLING_BACK_CONTEXT_RESULT_FILES = new String[] { "Hubert6", "Hubert" };
103
104 private static void initRollingBackRecovery() throws Throwable {
105 String txId = "ROLLING_BACK";
106 createTxContextFile(txId, STATUS_ROLLING_BACK_CONTEXT);
107 createTxDeleteFiles(txId, STATUS_ROLLING_BACK_CONTEXT_DELETE_FILES);
108 createTxChangeFiles(txId, STATUS_ROLLING_BACK_CONTEXT_CHANGE_FILES);
109 }
110
111 private static final String STATUS_ROLLEDBACK_CONTEXT =
112 "4\n10\n2000\n1063099404687\n";
113 private static final String[] STATUS_ROLLEDBACK_CONTEXT_CHANGE_FILES =
114 new String[] { "olli/Hubert4", "olli/Hubert5" };
115 private static final String[] STATUS_ROLLEDBACK_CONTEXT_DELETE_FILES = new String[] {
116 };
117 private static final String[] STATUS_ROLLEDBACK_CONTEXT_RESULT_FILES = new String[] { "Hubert6", "Hubert" };
118
119 private static void initRolledBackRecovery() throws Throwable {
120 String txId = "ROLLEDBACK";
121 createTxContextFile(txId, STATUS_ROLLEDBACK_CONTEXT);
122 createTxDeleteFiles(txId, STATUS_ROLLEDBACK_CONTEXT_DELETE_FILES);
123 createTxChangeFiles(txId, STATUS_ROLLEDBACK_CONTEXT_CHANGE_FILES);
124 }
125
126 private static final String STATUS_ACTIVE_CONTEXT = "0\n10\n2000\n1063099404687\n";
127 private static final String[] STATUS_ACTIVE_CONTEXT_CHANGE_FILES = new String[] { "olli/Hubert4", "olli/Hubert5" };
128 private static final String[] STATUS_ACTIVE_CONTEXT_DELETE_FILES = new String[] {
129 };
130 private static final String[] STATUS_ACTIVE_CONTEXT_RESULT_FILES = new String[] { "Hubert6", "Hubert" };
131
132 private static void initActiveRecovery() throws Throwable {
133 String txId = "ACTIVE";
134 createTxContextFile(txId, STATUS_ACTIVE_CONTEXT);
135 createTxDeleteFiles(txId, STATUS_ACTIVE_CONTEXT_DELETE_FILES);
136 createTxChangeFiles(txId, STATUS_ACTIVE_CONTEXT_CHANGE_FILES);
137 }
138
139 private static void removeRec(String dirPath) {
140 FileHelper.removeRec(new File(dirPath));
141 }
142
143 private static final void createFiles(String[] filePaths) {
144 createFiles(filePaths, null, null);
145 }
146
147 private static final void createFiles(String[] filePaths, String dirPath) {
148 createFiles(filePaths, null, dirPath);
149 }
150
151 private static final void createFiles(String[] filePaths, String[] contents) {
152 createFiles(filePaths, contents, null);
153 }
154
155 private static final void createFiles(String[] filePaths, String[] contents, String dirPath) {
156 for (int i = 0; i < filePaths.length; i++) {
157 String filePath = filePaths[i];
158 File file;
159 if (dirPath != null) {
160 file = new File(new File(dirPath), filePath);
161 } else {
162 file = new File(filePath);
163 }
164 file.getParentFile().mkdirs();
165 try {
166 file.delete();
167 file.createNewFile();
168 String content = null;
169 if (contents != null && contents.length > i) {
170 content = contents[i];
171 }
172 if (content != null) {
173 FileOutputStream stream = new FileOutputStream(file);
174 stream.write(contents[i].getBytes(ENCODING));
175 stream.close();
176 }
177 } catch (IOException e) {
178 }
179 }
180 }
181
182 private static final void checkIsEmpty(String dirPath) {
183 checkExactlyContains(dirPath, null);
184 }
185 private static final void checkExactlyContains(String dirPath, String[] fileNames) {
186 checkExactlyContains(dirPath, fileNames, null);
187 }
188
189 private static final void checkExactlyContains(String dirPath, String[] fileNames,
190 String[] contents) {
191 File dir = new File(dirPath);
192
193 if (dir.isDirectory()) {
194 File[] files = dir.listFiles();
195 if (fileNames == null) {
196 if (files.length != 0) {
197 fail(dirPath + " must be empty");
198 } else {
199 return;
200 }
201 }
202
203 if (files.length != fileNames.length) {
204 fail(dirPath + " contains " + files.length + " instead of " + fileNames.length
205 + " files");
206 }
207
208 for (int i = 0; i < fileNames.length; i++) {
209 String fileName = fileNames[i];
210 boolean match = false;
211 File file = null;
212 for (int j = 0; j < files.length; j++) {
213 file = files[j];
214 if (file.getName().equals(fileName)) {
215 match = true;
216 break;
217 }
218 }
219 if (!match) {
220 fail(dirPath + " does not contain required " + fileName);
221 }
222
223 String content = null;
224 if (contents != null && i < contents.length) {
225 content = contents[i];
226 }
227 if (content != null && !compare(file, content)) {
228 fail("Contents of " + fileName + " in " + dirPath
229 + " does not contain required content '" + content + "'");
230 }
231 }
232
233 } else {
234 fail(dirPath + " is not directoy");
235 }
236 }
237
238 private static boolean compare(FileInputStream stream, byte[] bytes) {
239 int read;
240 int count = 0;
241 try {
242 while ((read = stream.read()) != -1) {
243 if (bytes[count++] != read) {
244 return false;
245 }
246 }
247 } catch (IOException e) {
248 return false;
249 }
250 return true;
251 }
252
253 private static boolean compare(File file, String content) {
254 FileInputStream stream = null;
255 try {
256 byte[] bytes = content.getBytes(ENCODING);
257 stream = new FileInputStream(file);
258 return compare(stream, bytes);
259 } catch (Throwable t) {
260 return false;
261 } finally {
262 if (stream != null) {
263 try {
264 stream.close();
265 } catch (IOException e) {
266 }
267 }
268 }
269 }
270
271 private static String workForTx(Object txId) {
272 return WORK + "/" + txId;
273 }
274
275 private static String changeForTx(Object txId) {
276 return workForTx(txId) + "/change";
277 }
278
279 private static String deleteForTx(Object txId) {
280 return workForTx(txId) + "/delete";
281 }
282
283 private static String logForTx(Object txId) {
284 return workForTx(txId) + "/transaction.log";
285 }
286
287 private static void reset() {
288 removeRec(STORE);
289 removeRec(WORK);
290 new File(STORE).mkdirs();
291 new File(WORK).mkdirs();
292 }
293
294 private static void createInitialFiles() {
295 createFiles(INITIAL_FILES);
296 }
297
298 private static void createTxContextFile(Object txId, String content) {
299 createFiles(new String[] { logForTx(txId)}, new String[] { txId + "\n" + content });
300 }
301
302 private static void createTxDeleteFiles(Object txId, String[] files) {
303 createFiles(files, deleteForTx(txId));
304 }
305
306 private static void createTxChangeFiles(Object txId, String[] files) {
307 createFiles(files, changeForTx(txId));
308 }
309
310
311 private static void report(String should, String is) {
312 if (!is.equals(should)) {
313 fail("\nWrong output:\n'" + is + "'\nShould be:\n'" + should + "'\n");
314 }
315 }
316
317 public static FileResourceManager createFRM() {
318 return new FileResourceManager(STORE, WORK, false, sLogger, true);
319 }
320
321 public static Test suite() {
322 TestSuite suite = new TestSuite(FileResourceManagerTest.class);
323 return suite;
324 }
325
326 public static void main(java.lang.String[] args) {
327 junit.textui.TestRunner.run(suite());
328 }
329
330 public FileResourceManagerTest(String testName) {
331 super(testName);
332 }
333
334 public void testGlobal() throws Throwable {
335 reset();
336 createInitialFiles();
337
338 final FileResourceManager rm = createFRM();
339
340 rm.start();
341
342 final RendezvousBarrier shutdownBarrier = new RendezvousBarrier("Shutdown", 3, BARRIER_TIMEOUT, sLogger);
343 final RendezvousBarrier start2Barrier = new RendezvousBarrier("Start2", BARRIER_TIMEOUT, sLogger);
344 final RendezvousBarrier commit1Barrier = new RendezvousBarrier("Commit1", BARRIER_TIMEOUT, sLogger);
345
346 final Object txId1 = "Create";
347
348 Thread create = new Thread(new Runnable() {
349 public void run() {
350 try {
351 rm.startTransaction(txId1);
352
353 shutdownBarrier.call();
354 start2Barrier.call();
355
356 rm.createResource(txId1, "/olli/Hubert4");
357 rm.createResource(txId1, "/olli/Hubert5");
358 String msg = "Greetings from " + txId1 + "\n";
359 OutputStream out = rm.writeResource(txId1, "/olli/Hubert6");
360 out.write(msg.getBytes(ENCODING));
361
362 commit1Barrier.meet();
363
364 checkExactlyContains(
365 changeForTx(txId1) + "/olli",
366 new String[] { "Hubert4", "Hubert5", "Hubert6" },
367 new String[] { "", "", "Greetings from " + txId1 + "\n" });
368
369 rm.commitTransaction(txId1);
370
371 checkExactlyContains(
372 STORE + "/olli",
373 new String[] { "Hubert", "Hubert4", "Hubert5", "Hubert6" },
374 new String[] { "", "", "", "Greetings from " + txId1 + "\n" });
375
376 } catch (Throwable e) {
377 System.err.println("Error: " + e);
378 e.printStackTrace();
379 }
380 }
381 }, "Create Thread");
382
383 Thread modify = new Thread(new Runnable() {
384 public void run() {
385 Object txId = null;
386 try {
387
388 {
389 InputStream in = rm.readResource("/olli/Hubert6");
390 BufferedReader reader = new BufferedReader(new InputStreamReader(in, ENCODING));
391 String line = reader.readLine();
392 assertEquals(line, null);
393 in.close();
394 }
395
396 txId = "Modify";
397 rm.startTransaction(txId);
398 rm.setIsolationLevel(txId, ResourceManager.ISOLATION_LEVEL_READ_COMMITTED);
399
400 {
401 InputStream in = rm.readResource(txId, "/olli/Hubert6");
402 BufferedReader reader = new BufferedReader(new InputStreamReader(in, ENCODING));
403 String line = reader.readLine();
404 assertEquals(line, null);
405 in.close();
406 }
407
408 shutdownBarrier.call();
409
410 rm.createResource(txId, "/olli/Hubert1");
411 rm.createResource(txId, "/olli/Hubert2");
412 rm.createResource(txId, "/olli/Hubert3");
413
414
415
416 commit1Barrier.meet();
417
418 rm.createResource(txId, "/olli/Hubert4");
419 rm.createResource(txId, "/olli/Hubert5");
420
421 rm.createResource(txId, "/olli/Hubert6");
422 InputStream in = rm.readResource(txId, "/olli/Hubert6");
423 BufferedReader reader = new BufferedReader(new InputStreamReader(in, ENCODING));
424 String line = reader.readLine();
425
426 report("Greetings from " + txId1, line);
427 in.close();
428
429 rm.deleteResource(txId, "/olli/Hubert");
430 rm.deleteResource(txId, "/olli/Hubert2");
431 rm.deleteResource(txId, "/olli/Hubert3");
432 rm.deleteResource(txId, "/olli/Hubert4");
433 rm.deleteResource(txId, "/olli/Hubert5");
434
435 checkExactlyContains(deleteForTx(txId) + "/olli", new String[] { "Hubert", "Hubert4", "Hubert5" });
436
437 checkExactlyContains(changeForTx(txId) + "/olli", new String[] { "Hubert1" });
438
439 rm.commitTransaction(txId);
440 } catch (Throwable e) {
441 System.err.println("Error: " + e);
442 e.printStackTrace();
443 }
444 }
445 }, "Modify Thread");
446
447 create.start();
448
449 start2Barrier.meet();
450 modify.start();
451
452
453 shutdownBarrier.meet();
454
455 assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));
456
457 checkExactlyContains(
458 STORE + "/olli",
459 new String[] { "Hubert1", "Hubert6" },
460 new String[] { "", "Greetings from " + txId1 + "\n" });
461 checkIsEmpty(WORK);
462 }
463
464 public void testCombinedRecovery() throws Throwable {
465 reset();
466 createInitialFiles();
467 initCommittingRecovery();
468 initCommittedRecovery();
469 initActiveRecovery();
470 initRolledBackRecovery();
471 initRollingBackRecovery();
472
473 FileResourceManager rm =createFRM();
474
475
476 rm.start();
477 assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));
478
479
480 checkExactlyContains(STORE + "/olli", STATUS_COMMITTING_CONTEXT_RESULT_FILES);
481 checkIsEmpty(WORK);
482 }
483
484 public void testCommittingRecovery() throws Throwable {
485 reset();
486 createInitialFiles();
487 initCommittingRecovery();
488
489 FileResourceManager rm = createFRM();
490
491
492 rm.start();
493 assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));
494
495 checkExactlyContains(STORE + "/olli", STATUS_COMMITTING_CONTEXT_RESULT_FILES);
496 checkIsEmpty(WORK);
497 }
498
499 public void testActiveRecovery() throws Throwable {
500 reset();
501 createInitialFiles();
502 initActiveRecovery();
503
504 FileResourceManager rm = createFRM();
505
506
507 rm.start();
508 assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));
509
510 checkExactlyContains(STORE + "/olli", STATUS_ACTIVE_CONTEXT_RESULT_FILES);
511 checkIsEmpty(WORK);
512 }
513
514 public void testRolledbackRecovery() throws Throwable {
515 reset();
516 createInitialFiles();
517 initRolledBackRecovery();
518
519 FileResourceManager rm = createFRM();
520
521
522 rm.start();
523 assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));
524
525 checkExactlyContains(STORE + "/olli", STATUS_ROLLEDBACK_CONTEXT_RESULT_FILES);
526 checkIsEmpty(WORK);
527 }
528
529 public void testRollingBackRecovery() throws Throwable {
530 reset();
531 createInitialFiles();
532 initRollingBackRecovery();
533
534 FileResourceManager rm = createFRM();
535
536
537 rm.start();
538 assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));
539
540 checkExactlyContains(STORE + "/olli", STATUS_ROLLING_BACK_CONTEXT_RESULT_FILES);
541 checkIsEmpty(WORK);
542 }
543
544 public void testCommittedRecovery() throws Throwable {
545 reset();
546 createInitialFiles();
547 initCommittedRecovery();
548
549 FileResourceManager rm = createFRM();
550
551
552 rm.start();
553 assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));
554
555 checkExactlyContains(STORE + "/olli", STATUS_COMMITTED_CONTEXT_RESULT_FILES);
556 checkIsEmpty(WORK);
557 }
558
559 public void testInteractiveDirtyRecovery() throws Throwable {
560 reset();
561 createInitialFiles();
562
563 FileResourceManager rm = createFRM();
564
565 rm.start();
566
567 String txId = "DIRTY";
568 rm.startTransaction(txId);
569 rm.createResource(txId, "/olli/Hubert100");
570
571
572 FileResourceManager.TransactionContext context = rm.getContext(txId);
573
574 synchronized (context) {
575 sLogger.logFine("Committing Tx " + txId);
576
577 context.status = Status.STATUS_COMMITTING;
578 context.saveState();
579 rm.dirty = true;
580 context.finalCleanUp();
581 context.notifyFinish();
582 }
583
584
585 rm.readResource(txId, "/olli/Hubert");
586
587
588 boolean writeDeniedByDirty = false;
589 try {
590 rm.createResource(txId, "/olli/Hubert10");
591 } catch (ResourceManagerSystemException rmse) {
592 writeDeniedByDirty = true;
593 }
594 assertTrue(writeDeniedByDirty);
595
596
597 rm.recover();
598
599
600 txId = "DIRTYTEST";
601 rm.startTransaction(txId);
602 rm.readResource(txId, "/olli/Hubert");
603 rm.createResource(txId, "/olli/Hubert10");
604 rm.commitTransaction(txId);
605
606 assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));
607
608
609 checkExactlyContains(STORE + "/olli", new String[] { "Hubert", "Hubert100", "Hubert6", "Hubert10" });
610 checkIsEmpty(WORK);
611 }
612
613 public void testConflict() throws Throwable {
614 sLogger.logInfo("Checking concurrent transaction features");
615
616 reset();
617 createInitialFiles();
618
619 final FileResourceManager rm = createFRM();
620
621 rm.start();
622
623 final RendezvousBarrier restart = new RendezvousBarrier("restart",
624 TIMEOUT, sLogger);
625
626 for (int i = 0; i < 25; i++) {
627
628 final RendezvousBarrier deadlockBarrier1 = new RendezvousBarrier("deadlock" + i,
629 TIMEOUT, sLogger);
630
631 Thread thread1 = new Thread(new Runnable() {
632 public void run() {
633 try {
634 rm.startTransaction("tx1");
635
636 rm.createResource("tx1", "key2");
637 synchronized (deadlockBarrier1) {
638 deadlockBarrier1.meet();
639 deadlockBarrier1.reset();
640 }
641
642
643 rm.createResource("tx1", "key1");
644 rm.commitTransaction("tx1");
645 } catch (InterruptedException ie) {
646 } catch (ResourceManagerException e) {
647 assertEquals(e.getStatus(), ResourceManagerErrorCodes.ERR_DEAD_LOCK);
648 deadlockCnt++;
649 try {
650 rm.rollbackTransaction("tx1");
651 } catch (ResourceManagerException e1) {
652
653 e1.printStackTrace();
654 }
655 } finally {
656 try {
657 synchronized (restart) {
658 restart.meet();
659 restart.reset();
660 }
661 } catch (InterruptedException ie) {}
662
663 }
664 }
665 }, "Thread1");
666
667 thread1.start();
668
669 rm.startTransaction("tx2");
670 try {
671
672 rm.deleteResource("tx2", "key1");
673 synchronized (deadlockBarrier1) {
674 deadlockBarrier1.meet();
675 deadlockBarrier1.reset();
676 }
677
678
679 rm.deleteResource("tx2", "key2");
680 rm.commitTransaction("tx2");
681 } catch (ResourceManagerException e) {
682 assertEquals(e.getStatus(), ResourceManagerErrorCodes.ERR_DEAD_LOCK);
683 deadlockCnt++;
684 try {
685 rm.rollbackTransaction("tx2");
686 } catch (ResourceManagerException e1) {
687
688 e1.printStackTrace();
689 }
690 } finally {
691 try {
692 synchronized (restart) {
693 restart.meet();
694 restart.reset();
695 }
696 } catch (InterruptedException ie) {}
697
698 }
699
700
701
702 if (deadlockCnt != 1) {
703 sLogger.logWarning("More than one thread was deadlock victim!");
704 }
705 assertTrue(deadlockCnt >= 1);
706 deadlockCnt = 0;
707 }
708 }
709
710 public void testCopyRec() throws Throwable {
711 sLogger.logInfo("Checking file copy");
712 reset();
713 createInitialFiles();
714 FileHelper.copyRec(new File(INITIAL_FILES[0]), new File(STORE + "/olli/NewFile"));
715 }
716
717 }