Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1464115
Enhancement implemented #2954 : Recover from fatal invalid cache state
Dec 23, 2022
7f28e7c
Enhancement #2954 : Recover from fatal invalid cache state
Jan 18, 2023
a6a61b1
Merge branch 'ehcache:master' into issue-2954
jitendra-nalwaya Feb 8, 2023
a6b7039
Enhancement ehcache#2954 : Recover from fatal invalid cache state.
Feb 10, 2023
5012e92
Enhancement ehcache#2954 : Recover from fatal invalid cache state
Feb 16, 2023
a6265de
Enhancement ehcache#2954 : Recover from fatal invalid cache state.
Mar 3, 2023
ecf166c
Merge branch 'ehcache:master' into issue-2954
jitendra-nalwaya Mar 30, 2023
0f5098a
Enhancement ehcache#2954 : Recover from fatal invalid cache state.
Mar 30, 2023
ceebfe4
Enhancement ehcache#2954 : Recover from fatal invalid cache state.
Apr 6, 2023
ea0eb35
Enhancement ehcache#2954 : Recover from fatal invalid cache state.
Apr 6, 2023
2fbc98c
Enhancement ehcache#2954 : Recover from fatal invalid cache state.
Apr 10, 2023
76287e7
Enhancement ehcache#2954 : Recover from fatal invalid cache state.
Apr 11, 2023
93969c4
Merge branch 'ehcache:master' into issue-2954
jitendra-nalwaya Apr 19, 2023
976a137
Enhancement ehcache#2954 : Recover from fatal invalid cache state.
Apr 24, 2023
79d042e
Merge branch 'ehcache:master' into issue-2954
jitendra-nalwaya May 5, 2023
cc87d58
Enhancement ehcache#2954 : Recover from fatal invalid cache state.
May 5, 2023
c6cfcab
Refactor: Removed null checks.
May 15, 2023
15821b9
Enhancement ehcache#2954 : Recover from fatal invalid cache state.
May 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@ interface SafeSpaceIdentifier {
}

/**
* Identify state(normal/abnormal) of stopped service.
* @return <tt>true</tt> if service stopped normally.
* Return the cleanliness of the state stored in this service.
* <p>
* Stored state is assumed to be clean if the service detects
* that the last started instantiation of this service was shutdown
* successfully.
*
* @return {@code true} if the state is clean
* @throws IllegalStateException if the service is not started
*/
boolean isClean();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc needs polish, and the @throws should be moved up to here:

  /**
   * Return the cleanliness of the state stored in this service.
   * <p>
   * Stored state is assumed to be clean if the service detects
   * that the last started instantiation of this service was shutdown
   * successfully.
   *
   * @return {@code true} if the state is clean
   * @throws IllegalStateException if the service is not started
   */


/**
* Identify status of service.
* @return <tt>true</tt> if service is started.
*/
boolean isServiceStarted();
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ private boolean isStarted() {
@Override
public void start(final ServiceProvider<Service> serviceProvider) {
innerStart(serviceProvider);
if (persistenceService != null && !persistenceService.isClean()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to null-check here. Persistence service should always be non-null because we depend on it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chrisdennis
Seven existing test cases are getting failed in DefaultDiskResourceServiceTest.java ("WithoutPersistenceService") if not handling the null check. Please share your thoughts on this. Thanks.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to look at the @ServiceDependency annotation that is applied to this class, and think about how that annotation is interpreted by the ServiceLocator. Then note that the tests you're looking at are not using the ServiceLocator to provide the services to the DefaultDiskResourceService.

destroyAll();
LOGGER.info("Probably unclean shutdown was done, so deleted root directory.");
}
}

/**
Expand All @@ -73,10 +77,6 @@ public void startForMaintenance(ServiceProvider<? super MaintainableService> ser
private void innerStart(ServiceProvider<? super MaintainableService> serviceProvider) {
persistenceService = serviceProvider.getService(LocalPersistenceService.class);
isStarted = true;
if (persistenceService!=null && persistenceService.isServiceStarted() && !persistenceService.isClean()) {
destroyAll();
LOGGER.info("Probably unclean shutdown was done, so deleted root directory.");
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,14 @@ public synchronized void stop() {
if (cleanFile.createNewFile()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably log some kind of warning on a false return here too.

LOGGER.debug("clean file is created.");
} else {
LOGGER.warn("clean file already exists. It's not deleted either user's permission or network issue." +
"\n Hint: clean file exists on service startup, indicates service was stopped cleanly last time. It gets created while the service is stopped and it should be deleted while the service is started.");
LOGGER.warn("clean file already exists. The file didn't got deleted, may be due to network issue or file permission on directory." +
"\n Hint: clean file exists on service start-up, indicates service was stopped cleanly last time. It gets created while the service is stopped and it should be deleted while the service is started." +
"\n Action: Please verify there permission to delete the file and delete the root directory prior to start the service again.");
}
} catch (IOException e) {
LOGGER.warn("clean file is not created. Reason: " + e.getMessage() +

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log line suffers the same problem. It's long, wordy, but doesn't help the user.

"\n Hint: clean file exists on service startup, indicates service was stopped cleanly last time. It gets created while the service is stopped and it should be deleted while the service is started.");
"\n Hint: clean file exists on service start-up, indicates service was stopped cleanly last time. It gets created while the service is stopped and it should be deleted while the service is started." +
"\n Action: Do resolve the exception received. Prior to start the service again, please delete the root directory.");
}
try {
lock.release();
Expand Down Expand Up @@ -237,10 +239,6 @@ public void destroyAll(String owner) {

/**

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This javadoc isn't needed once we fix up the interface method javadoc.

* {@inheritDoc}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to keep an {@inheritDoc} around... javadoc will get inherited anyway.

* Abnormally stopped service may lead to data corruption.
* Can take appropriate action by identifying state of service stopped.
*
* @throws IllegalStateException if service is not running.
*/
@Override
public final synchronized boolean isClean() {
Expand All @@ -251,14 +249,6 @@ public final synchronized boolean isClean() {
}
}

/**
* {@inheritDoc}
*/
@Override
public final boolean isServiceStarted() {
return started;
}

private void destroy(SafeSpace ss, boolean verbose) {
if (verbose) {
LOGGER.debug("Destroying file based persistence context for {}", ss.identifier);
Expand All @@ -270,7 +260,6 @@ private void destroy(SafeSpace ss, boolean verbose) {
}
}


private SafeSpace createSafeSpaceLogical(String owner, String identifier) {
File ownerDirectory = new File(rootDirectory, owner);
File directory = new File(ownerDirectory, safeIdentifier(identifier));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.ehcache.test.MockitoUtil.uncheckedGenericMock;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -74,7 +75,7 @@ public void testHandlesResourceType() {
@Test
public void testDestroyAll() {
service.destroyAll();
verify(persistenceService).destroyAll(DefaultDiskResourceService.PERSISTENCE_SPACE_OWNER);
verify(persistenceService, times(2)).destroyAll(DefaultDiskResourceService.PERSISTENCE_SPACE_OWNER);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,32 +144,44 @@ public void testExclusiveLock() throws IOException {
}

@Test
public void testServiceStoppedStatusWhenStoppedCleanly() throws IOException {
File f = folder.newFolder("testServiceStoppedStatusWhenStoppedCleanly");
public void testServiceShutdownWithEmptyDirectory() throws IOException {
File f = folder.newFolder("testServiceShutdownWithEmptyDirectory");
final DefaultLocalPersistenceService service = new DefaultLocalPersistenceService(new DefaultPersistenceConfiguration(f));
service.start(null);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should actually create a file inside the directory to ensure we hit the non-empty directory codepath.

assertTrue(service.isClean());
service.stop();
service.start(null);
assertTrue(service.isClean());
service.stop();
}

@Test
public void testServiceStoppedStatusWhenStoppedUnexpectedly() throws IOException {
// Service stopped unexpectedly means directory exists with some data but without .clean file.
File f = folder.newFolder("testServiceStoppedStatusWhenStoppedUnexpectedly");
public void testServiceShutdownWithNonEmptyDirectory() throws IOException {
File f = folder.newFolder("testServiceShutdownWithNonEmptyDirectory");
final DefaultLocalPersistenceService service = new DefaultLocalPersistenceService(new DefaultPersistenceConfiguration(f));
new File(f, "dummy.txt").createNewFile();
new File(f, ".clean").createNewFile();
service.start(null);
assertTrue(service.isClean());
service.stop();
}

@Test
public void testServiceShutdownUnexpectedly() throws IOException {
// Service shutdown unexpectedly means directory exists with some data but without .clean file.
File f = folder.newFolder("testServiceShutdownUnexpectedly");
final DefaultLocalPersistenceService service = new DefaultLocalPersistenceService(new DefaultPersistenceConfiguration(f));
new File(f.getAbsolutePath()+"\\dummy.txt").createNewFile();
new File(f, "dummy.txt").createNewFile();
service.start(null);
assertFalse(service.isClean());
service.stop();
}

@Test
public void testServiceStoppedStatusIfServiceIsNotRunning() throws IOException {
public void testServiceShutdownStatusIfServiceIsNotRunning() throws IOException {
File f = folder.newFolder("testServiceShutdownStatusIfServiceIsNotRunning");
final DefaultLocalPersistenceService service = new DefaultLocalPersistenceService(new DefaultPersistenceConfiguration(f));
try {
File f = folder.newFolder("testServiceStoppedStatusIfServiceIsNotRunning");
final DefaultLocalPersistenceService service = new DefaultLocalPersistenceService(new DefaultPersistenceConfiguration(f));
service.isClean();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the method call being tested should be inside the try-catch block.

fail("Expected IllegalStateException");
} catch(IllegalStateException e) {
Expand Down