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