001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: TxnTest.java,v 1.58.2.3 2008/01/07 15:14:34 cwl Exp $
007: */
008:
009: package com.sleepycat.je.txn;
010:
011: import java.io.File;
012: import java.io.IOException;
013: import java.io.RandomAccessFile;
014:
015: import junit.framework.TestCase;
016:
017: import com.sleepycat.je.Cursor;
018: import com.sleepycat.je.Database;
019: import com.sleepycat.je.DatabaseConfig;
020: import com.sleepycat.je.DatabaseEntry;
021: import com.sleepycat.je.DatabaseException;
022: import com.sleepycat.je.DbInternal;
023: import com.sleepycat.je.DeadlockException;
024: import com.sleepycat.je.Environment;
025: import com.sleepycat.je.EnvironmentConfig;
026: import com.sleepycat.je.EnvironmentMutableConfig;
027: import com.sleepycat.je.LockMode;
028: import com.sleepycat.je.LockNotGrantedException;
029: import com.sleepycat.je.LockStats;
030: import com.sleepycat.je.OperationStatus;
031: import com.sleepycat.je.Transaction;
032: import com.sleepycat.je.TransactionConfig;
033: import com.sleepycat.je.config.EnvironmentParams;
034: import com.sleepycat.je.dbi.DatabaseImpl;
035: import com.sleepycat.je.dbi.EnvironmentImpl;
036: import com.sleepycat.je.dbi.MemoryBudget;
037: import com.sleepycat.je.log.FileManager;
038: import com.sleepycat.je.tree.ChildReference;
039: import com.sleepycat.je.tree.IN;
040: import com.sleepycat.je.tree.LN;
041: import com.sleepycat.je.tree.WithRootLatched;
042: import com.sleepycat.je.txn.Txn;
043: import com.sleepycat.je.util.TestUtils;
044:
045: /*
046: * Simple transaction testing
047: */
048: public class TxnTest extends TestCase {
049: private File envHome;
050: private Environment env;
051: private Database db;
052:
053: public TxnTest() throws DatabaseException {
054:
055: envHome = new File(System.getProperty(TestUtils.DEST_DIR));
056: }
057:
058: public void setUp() throws IOException, DatabaseException {
059:
060: IN.ACCUMULATED_LIMIT = 0;
061: Txn.ACCUMULATED_LIMIT = 0;
062:
063: TestUtils.removeFiles("Setup", envHome, FileManager.JE_SUFFIX);
064:
065: EnvironmentConfig envConfig = TestUtils.initEnvConfig();
066: envConfig.setConfigParam(EnvironmentParams.NODE_MAX.getName(),
067: "6");
068: envConfig.setTransactional(true);
069: envConfig.setAllowCreate(true);
070: env = new Environment(envHome, envConfig);
071:
072: DatabaseConfig dbConfig = new DatabaseConfig();
073: dbConfig.setTransactional(true);
074: dbConfig.setAllowCreate(true);
075: db = env.openDatabase(null, "foo", dbConfig);
076: }
077:
078: public void tearDown() throws IOException, DatabaseException {
079:
080: db.close();
081: env.close();
082: TestUtils.removeFiles("TearDown", envHome,
083: FileManager.JE_SUFFIX);
084: }
085:
086: /**
087: * Test transaction locking and releasing
088: */
089: public void testBasicLocking() throws Throwable {
090:
091: try {
092:
093: LN ln = new LN(new byte[0]);
094:
095: /*
096: * Make a null txn that will lock. Take a lock and then end the
097: * operation.
098: */
099: EnvironmentImpl envImpl = DbInternal
100: .envGetEnvironmentImpl(env);
101: MemoryBudget mb = envImpl.getMemoryBudget();
102:
103: long beforeLock = mb.getCacheMemoryUsage();
104: Locker nullTxn = new BasicLocker(envImpl);
105:
106: LockGrantType lockGrant = nullTxn.lock(ln.getNodeId(),
107: LockType.READ, false,
108: DbInternal.dbGetDatabaseImpl(db)).getLockGrant();
109: assertEquals(LockGrantType.NEW, lockGrant);
110: long afterLock = mb.getCacheMemoryUsage();
111: checkHeldLocks(nullTxn, 1, 0);
112:
113: nullTxn.operationEnd();
114: long afterRelease = mb.getCacheMemoryUsage();
115: checkHeldLocks(nullTxn, 0, 0);
116: checkCacheUsage(beforeLock, afterLock, afterRelease,
117: LockManager.TOTAL_LOCK_OVERHEAD
118: + MemoryBudget.LOCKINFO_OVERHEAD);
119:
120: /* Take a lock, release it. */
121: beforeLock = mb.getCacheMemoryUsage();
122: lockGrant = nullTxn.lock(ln.getNodeId(), LockType.READ,
123: false, DbInternal.dbGetDatabaseImpl(db))
124: .getLockGrant();
125: afterLock = mb.getCacheMemoryUsage();
126: assertEquals(LockGrantType.NEW, lockGrant);
127: checkHeldLocks(nullTxn, 1, 0);
128:
129: nullTxn.releaseLock(ln.getNodeId());
130: checkHeldLocks(nullTxn, 0, 0);
131: afterRelease = mb.getCacheMemoryUsage();
132: checkCacheUsage(beforeLock, afterLock, afterRelease,
133: LockManager.TOTAL_LOCK_OVERHEAD
134: + MemoryBudget.LOCKINFO_OVERHEAD);
135:
136: /*
137: * Make a user transaction, check lock and release.
138: */
139: beforeLock = mb.getCacheMemoryUsage();
140: Txn userTxn = new Txn(envImpl, new TransactionConfig());
141: lockGrant = userTxn.lock(ln.getNodeId(), LockType.READ,
142: false, DbInternal.dbGetDatabaseImpl(db))
143: .getLockGrant();
144: afterLock = mb.getCacheMemoryUsage();
145:
146: assertEquals(LockGrantType.NEW, lockGrant);
147: checkHeldLocks(userTxn, 1, 0);
148:
149: /* Try demoting, nothing should happen. */
150: try {
151: userTxn.demoteLock(ln.getNodeId());
152: fail("exception not thrown on phoney demoteLock");
153: } catch (AssertionError e) {
154: }
155: checkHeldLocks(userTxn, 1, 0);
156: long afterDemotion = mb.getCacheMemoryUsage();
157: assertEquals(afterLock, afterDemotion);
158:
159: /* Make it a write lock, then demote. */
160: lockGrant = userTxn.lock(ln.getNodeId(), LockType.WRITE,
161: false, DbInternal.dbGetDatabaseImpl(db))
162: .getLockGrant();
163: assertEquals(LockGrantType.PROMOTION, lockGrant);
164: long afterWriteLock = mb.getCacheMemoryUsage();
165: assertTrue(afterWriteLock > afterLock);
166: assertTrue(afterLock > beforeLock);
167:
168: checkHeldLocks(userTxn, 0, 1);
169: userTxn.demoteLock(ln.getNodeId());
170: checkHeldLocks(userTxn, 1, 0);
171:
172: /* Shouldn't release at operation end. */
173: userTxn.operationEnd();
174: checkHeldLocks(userTxn, 1, 0);
175:
176: userTxn.releaseLock(ln.getNodeId());
177: checkHeldLocks(userTxn, 0, 0);
178: userTxn.commit(Txn.TXN_SYNC);
179: afterRelease = mb.getCacheMemoryUsage();
180: assertTrue(afterLock > beforeLock);
181: } catch (Throwable t) {
182: /* print stack trace before going to teardown. */
183: t.printStackTrace();
184: throw t;
185: }
186: }
187:
188: private void checkHeldLocks(Locker txn, int numReadLocks,
189: int numWriteLocks) throws DatabaseException {
190:
191: LockStats stat = txn.collectStats(new LockStats());
192: assertEquals(numReadLocks, stat.getNReadLocks());
193: assertEquals(numWriteLocks, stat.getNWriteLocks());
194: }
195:
196: /**
197: * Test transaction commit, from the locking point of view.
198: */
199: public void testCommit() throws Throwable {
200:
201: try {
202: LN ln1 = new LN(new byte[0]);
203: LN ln2 = new LN(new byte[0]);
204:
205: EnvironmentImpl envImpl = DbInternal
206: .envGetEnvironmentImpl(env);
207: Txn userTxn = new Txn(envImpl, new TransactionConfig());
208:
209: /* Get read lock 1. */
210: LockGrantType lockGrant = userTxn.lock(ln1.getNodeId(),
211: LockType.READ, false,
212: DbInternal.dbGetDatabaseImpl(db)).getLockGrant();
213: assertEquals(LockGrantType.NEW, lockGrant);
214: checkHeldLocks(userTxn, 1, 0);
215:
216: /* Get read lock 2. */
217: lockGrant = userTxn.lock(ln2.getNodeId(), LockType.READ,
218: false, DbInternal.dbGetDatabaseImpl(db))
219: .getLockGrant();
220: assertEquals(LockGrantType.NEW, lockGrant);
221: checkHeldLocks(userTxn, 2, 0);
222:
223: /* Upgrade read lock 2 to a write. */
224: lockGrant = userTxn.lock(ln2.getNodeId(), LockType.WRITE,
225: false, DbInternal.dbGetDatabaseImpl(db))
226: .getLockGrant();
227: assertEquals(LockGrantType.PROMOTION, lockGrant);
228: checkHeldLocks(userTxn, 1, 1);
229:
230: /* Read lock 1 again, shouldn't increase count. */
231: lockGrant = userTxn.lock(ln1.getNodeId(), LockType.READ,
232: false, DbInternal.dbGetDatabaseImpl(db))
233: .getLockGrant();
234: assertEquals(LockGrantType.EXISTING, lockGrant);
235: checkHeldLocks(userTxn, 1, 1);
236:
237: /* Shouldn't release at operation end. */
238: long commitLsn = userTxn.commit(Txn.TXN_SYNC);
239: checkHeldLocks(userTxn, 0, 0);
240:
241: TxnCommit commitRecord = (TxnCommit) envImpl
242: .getLogManager().get(commitLsn);
243:
244: assertEquals(userTxn.getId(), commitRecord.getId());
245: assertEquals(userTxn.getLastLsn(), commitRecord
246: .getLastLsn());
247: } catch (Throwable t) {
248: /* Print stack trace before going to teardown. */
249: t.printStackTrace();
250: throw t;
251: }
252: }
253:
254: /**
255: * Make sure an abort never tries to split the tree.
256: */
257: public void testAbortNoSplit() throws Throwable {
258:
259: try {
260: Transaction txn = env.beginTransaction(null, null);
261:
262: DatabaseEntry keyDbt = new DatabaseEntry();
263: DatabaseEntry dataDbt = new DatabaseEntry();
264: dataDbt.setData(new byte[1]);
265:
266: /* Insert enough data so that the tree is ripe for a split. */
267: int numForSplit = 25;
268: for (int i = 0; i < numForSplit; i++) {
269: keyDbt.setData(TestUtils.getTestArray(i));
270: db.put(txn, keyDbt, dataDbt);
271: }
272:
273: /* Check that we're ready for a split. */
274: DatabaseImpl database = DbInternal.dbGetDatabaseImpl(db);
275: CheckReadyToSplit splitChecker = new CheckReadyToSplit(
276: database);
277: database.getTree().withRootLatchedShared(splitChecker);
278: assertTrue(splitChecker.getReadyToSplit());
279:
280: /*
281: * Make another txn that will get a read lock on the map
282: * LSN. Then abort the first txn. It shouldn't try to do a
283: * split, if it does, we'll run into the
284: * no-latches-while-locking check.
285: */
286: Transaction txnSpoiler = env.beginTransaction(null, null);
287: DatabaseConfig dbConfig = new DatabaseConfig();
288: dbConfig.setTransactional(true);
289: Database dbSpoiler = env.openDatabase(txnSpoiler, "foo",
290: dbConfig);
291:
292: txn.abort();
293:
294: /*
295: * The database should be empty
296: */
297: Cursor cursor = dbSpoiler.openCursor(txnSpoiler, null);
298:
299: assertTrue(cursor.getFirst(keyDbt, dataDbt,
300: LockMode.DEFAULT) != OperationStatus.SUCCESS);
301: cursor.close();
302: txnSpoiler.abort();
303: } catch (Throwable t) {
304: /* print stack trace before going to teardown. */
305: t.printStackTrace();
306: throw t;
307: }
308: }
309:
310: public void testTransactionName() throws Throwable {
311:
312: try {
313: Transaction txn = env.beginTransaction(null, null);
314: txn.setName("blort");
315: assertEquals("blort", txn.getName());
316: txn.abort();
317:
318: /*
319: * [#14349] Make sure the txn is printable after closing. We
320: * once had a NullPointerException.
321: */
322: String s = txn.toString();
323: } catch (Throwable t) {
324: /* print stack trace before going to teardown. */
325: t.printStackTrace();
326: throw t;
327: }
328: }
329:
330: /**
331: * Test all combinations of sync, nosync, and writeNoSync for txn
332: * commits.
333: */
334:
335: /* SyncCombo expresses all the combinations of txn sync properties. */
336: private static class SyncCombo {
337: private boolean envNoSync;
338: private boolean envWriteNoSync;
339: private boolean txnNoSync;
340: private boolean txnWriteNoSync;
341: private boolean txnSync;
342: boolean expectSync;
343: boolean expectWrite;
344:
345: SyncCombo(int envWriteNoSync, int envNoSync, int txnSync,
346: int txnWriteNoSync, int txnNoSync, boolean expectSync,
347: boolean expectWrite) {
348: this .envNoSync = (envNoSync == 0) ? false : true;
349: this .envWriteNoSync = (envWriteNoSync == 0) ? false : true;
350: this .txnNoSync = (txnNoSync == 0) ? false : true;
351: this .txnWriteNoSync = (txnWriteNoSync == 0) ? false : true;
352: this .txnSync = (txnSync == 0) ? false : true;
353: this .expectSync = expectSync;
354: this .expectWrite = expectWrite;
355: }
356:
357: TransactionConfig getTxnConfig() {
358: TransactionConfig txnConfig = new TransactionConfig();
359: txnConfig.setSync(txnSync);
360: txnConfig.setWriteNoSync(txnWriteNoSync);
361: txnConfig.setNoSync(txnNoSync);
362: return txnConfig;
363: }
364:
365: void setEnvironmentMutableConfig(Environment env)
366: throws DatabaseException {
367: EnvironmentMutableConfig config = env.getMutableConfig();
368: config.setTxnNoSync(envNoSync);
369: config.setTxnWriteNoSync(envWriteNoSync);
370: env.setMutableConfig(config);
371: }
372: }
373:
374: public void testSyncCombo() throws Throwable {
375:
376: RandomAccessFile logFile = new RandomAccessFile(new File(
377: envHome, "00000000.jdb"), "r");
378: try {
379: SyncCombo[] testCombinations = {
380: /* Env Env Txn Txn Txn Expect Expect
381: * WrNoSy NoSy Sync WrNoSy NoSyc Sync Write */
382: new SyncCombo(0, 0, 0, 0, 0, true, true),
383: new SyncCombo(0, 0, 0, 0, 1, false, false),
384: new SyncCombo(0, 0, 0, 1, 0, false, true),
385: new SyncCombo(0, 0, 0, 1, 1, false, true),
386: new SyncCombo(0, 0, 1, 0, 0, true, true),
387: new SyncCombo(0, 0, 1, 0, 1, true, true),
388: new SyncCombo(0, 0, 1, 1, 0, true, true),
389: new SyncCombo(0, 0, 1, 1, 1, true, true),
390: new SyncCombo(0, 1, 0, 0, 0, false, false),
391: new SyncCombo(0, 1, 0, 0, 1, false, false),
392: new SyncCombo(0, 1, 0, 1, 0, false, true),
393: new SyncCombo(0, 1, 0, 1, 1, false, true),
394: new SyncCombo(0, 1, 1, 0, 0, true, true),
395: new SyncCombo(0, 1, 1, 0, 1, true, true),
396: new SyncCombo(0, 1, 1, 1, 0, true, true),
397: new SyncCombo(0, 1, 1, 1, 1, true, true),
398: new SyncCombo(1, 0, 0, 0, 0, false, true),
399: new SyncCombo(1, 0, 0, 0, 1, false, false),
400: new SyncCombo(1, 0, 0, 1, 0, false, true),
401: new SyncCombo(1, 0, 0, 1, 1, false, true),
402: new SyncCombo(1, 0, 1, 0, 0, true, true),
403: new SyncCombo(1, 0, 1, 0, 1, true, true),
404: new SyncCombo(1, 0, 1, 1, 0, true, true),
405: new SyncCombo(1, 0, 1, 1, 1, true, true),
406: new SyncCombo(1, 1, 0, 0, 0, false, true),
407: new SyncCombo(1, 1, 0, 0, 1, false, false),
408: new SyncCombo(1, 1, 0, 1, 0, false, true),
409: new SyncCombo(1, 1, 0, 1, 1, false, true),
410: new SyncCombo(1, 1, 1, 0, 0, true, true),
411: new SyncCombo(1, 1, 1, 0, 1, true, true),
412: new SyncCombo(1, 1, 1, 1, 0, true, true),
413: new SyncCombo(1, 1, 1, 1, 1, true, true) };
414:
415: /* envNoSync=false with default env config */
416: assertTrue(!env.getMutableConfig().getTxnNoSync());
417:
418: /* envWriteNoSync=false with default env config */
419: assertTrue(!env.getMutableConfig().getTxnWriteNoSync());
420:
421: /*
422: * For each combination of settings, call commit and
423: * check that we have the expected sync and log
424: * write. Make sure that commitSync(), commitNoSync always
425: * override all preferences.
426: */
427: for (int i = 0; i < testCombinations.length; i++) {
428: SyncCombo combo = testCombinations[i];
429: TransactionConfig txnConfig = combo.getTxnConfig();
430: combo.setEnvironmentMutableConfig(env);
431: syncExplicit(logFile, txnConfig, combo.expectSync,
432: combo.expectWrite);
433: }
434:
435: SyncCombo[] autoCommitCombinations = {
436: /* Env Env Txn Txn Txn Expect Expect
437: * WrNoSy NoSy Sync WrNoSy NoSyc Sync Write */
438: new SyncCombo(0, 0, 0, 0, 0, true, true),
439: new SyncCombo(0, 1, 0, 0, 0, false, false),
440: new SyncCombo(1, 0, 0, 0, 0, false, true),
441: new SyncCombo(1, 1, 0, 0, 0, false, true) };
442:
443: for (int i = 0; i < autoCommitCombinations.length; i++) {
444: SyncCombo combo = autoCommitCombinations[i];
445: combo.setEnvironmentMutableConfig(env);
446: syncAutoCommit(logFile, combo.expectSync,
447: combo.expectWrite);
448: }
449: } catch (Throwable t) {
450: /* print stack trace before going to teardown. */
451: t.printStackTrace();
452: throw t;
453: } finally {
454: logFile.close();
455: }
456: }
457:
458: /**
459: * Does an explicit commit and returns whether an fsync occured.
460: */
461: private void syncExplicit(RandomAccessFile lastLogFile,
462: TransactionConfig config, boolean expectSync,
463: boolean expectWrite) throws DatabaseException, IOException {
464:
465: DatabaseEntry key = new DatabaseEntry(new byte[1]);
466: DatabaseEntry data = new DatabaseEntry(new byte[1]);
467:
468: long beforeSyncs = getNSyncs();
469: Transaction txn = env.beginTransaction(null, config);
470: db.put(txn, key, data);
471: long beforeLength = lastLogFile.length();
472: txn.commit();
473: long afterSyncs = getNSyncs();
474: long afterLength = lastLogFile.length();
475: boolean syncOccurred = afterSyncs > beforeSyncs;
476: boolean writeOccurred = afterLength > beforeLength;
477: assertEquals(expectSync, syncOccurred);
478: assertEquals(expectWrite, writeOccurred);
479:
480: /*
481: * Make sure explicit sync/noSync/writeNoSync always works.
482: */
483:
484: /* Expect a sync and write. */
485: beforeSyncs = getNSyncs();
486: beforeLength = lastLogFile.length();
487: txn = env.beginTransaction(null, config);
488: db.put(txn, key, data);
489: txn.commitSync();
490: afterSyncs = getNSyncs();
491: afterLength = lastLogFile.length();
492: assert (afterSyncs > beforeSyncs);
493: assert (afterLength > beforeLength);
494:
495: /* Expect neither a sync nor write. */
496: beforeSyncs = getNSyncs();
497: beforeLength = lastLogFile.length();
498: txn = env.beginTransaction(null, config);
499: db.put(txn, key, data);
500: txn.commitNoSync();
501: afterSyncs = getNSyncs();
502: afterLength = lastLogFile.length();
503: assert (afterSyncs == beforeSyncs);
504: assert (afterLength == beforeLength);
505:
506: /* Expect no sync but do expect a write. */
507: beforeSyncs = getNSyncs();
508: beforeLength = lastLogFile.length();
509: txn = env.beginTransaction(null, config);
510: db.put(txn, key, data);
511: txn.commitWriteNoSync();
512: afterSyncs = getNSyncs();
513: afterLength = lastLogFile.length();
514: assert (afterSyncs == beforeSyncs);
515: assert (afterLength > beforeLength);
516: }
517:
518: /**
519: * Does an auto-commit and returns whether an fsync occured.
520: */
521: private void syncAutoCommit(RandomAccessFile lastLogFile,
522: boolean expectSync, boolean expectWrite)
523: throws DatabaseException, IOException {
524:
525: DatabaseEntry key = new DatabaseEntry(new byte[1]);
526: DatabaseEntry data = new DatabaseEntry(new byte[1]);
527: long beforeSyncs = getNSyncs();
528: long beforeLength = lastLogFile.length();
529: db.put(null, key, data);
530: long afterLength = lastLogFile.length();
531: long afterSyncs = getNSyncs();
532: boolean syncOccurred = afterSyncs > beforeSyncs;
533: assertEquals(expectSync, syncOccurred);
534: assertEquals(expectWrite, (afterLength > beforeLength));
535: }
536:
537: /**
538: * Returns number of fsyncs statistic.
539: */
540: private long getNSyncs() {
541: return DbInternal.envGetEnvironmentImpl(env).getFileManager()
542: .getNFSyncs();
543: }
544:
545: public void testNoWaitConfig() throws Throwable {
546:
547: try {
548: TransactionConfig defaultConfig = new TransactionConfig();
549: TransactionConfig noWaitConfig = new TransactionConfig();
550: noWaitConfig.setNoWait(true);
551: Transaction txn;
552:
553: /* noWait=false */
554:
555: assertTrue(!isNoWaitTxn(null));
556:
557: txn = env.beginTransaction(null, null);
558: assertTrue(!isNoWaitTxn(txn));
559: txn.abort();
560:
561: txn = env.beginTransaction(null, defaultConfig);
562: assertTrue(!isNoWaitTxn(txn));
563: txn.abort();
564:
565: /* noWait=true */
566:
567: txn = env.beginTransaction(null, noWaitConfig);
568: assertTrue(isNoWaitTxn(txn));
569: txn.abort();
570:
571: } catch (Throwable t) {
572: /* print stack trace before going to teardown. */
573: t.printStackTrace();
574: throw t;
575: }
576: }
577:
578: /**
579: * Returns whether the given txn is a no-wait txn, or if the txn parameter
580: * is null returns whether an auto-commit txn is a no-wait txn.
581: */
582: private boolean isNoWaitTxn(Transaction txn)
583: throws DatabaseException {
584:
585: DatabaseEntry key = new DatabaseEntry(new byte[1]);
586: DatabaseEntry data = new DatabaseEntry(new byte[1]);
587:
588: /* Use a wait txn to get a write lock. */
589: Transaction txn2 = env.beginTransaction(null, null);
590: db.put(txn2, key, data);
591:
592: try {
593: db.put(txn, key, data);
594: throw new IllegalStateException(
595: "Lock should not have been granted");
596: } catch (LockNotGrantedException e) {
597: return true;
598: } catch (DeadlockException e) {
599: return false;
600: } finally {
601: txn2.abort();
602: }
603: }
604:
605: /*
606: * Assert that cache utilization is correctly incremented by locks and
607: * txns, and decremented after release.
608: */
609: private void checkCacheUsage(long beforeLock, long afterLock,
610: long afterRelease, long expectedSize) {
611: assertEquals(beforeLock, afterRelease);
612: assertEquals(afterLock, (beforeLock + expectedSize));
613: }
614:
615: class CheckReadyToSplit implements WithRootLatched {
616: private boolean readyToSplit;
617: private DatabaseImpl database;
618:
619: CheckReadyToSplit(DatabaseImpl database) {
620: readyToSplit = false;
621: this .database = database;
622: }
623:
624: public boolean getReadyToSplit() {
625: return readyToSplit;
626: }
627:
628: public IN doWork(ChildReference root) throws DatabaseException {
629:
630: IN rootIN = (IN) root.fetchTarget(database, null);
631: readyToSplit = rootIN.needsSplitting();
632: return null;
633: }
634: }
635: }
|