Add shutdown

This commit is contained in:
René Pfeuffer
2020-04-27 08:03:30 +02:00
parent 7ddb528414
commit 5f96244b9b
9 changed files with 173 additions and 7 deletions

View File

@@ -30,4 +30,6 @@ public interface CacheSupportingWorkdirProvider {
<R, W, C> SimpleWorkdirFactory.ParentAndClone<R, W> getWorkdir(CreateWorkdirContext<R, W, C> context) throws Exception;
void contextClosed(CreateWorkdirContext<?, ?, ?> createWorkdirContext, File workdir) throws Exception;
void shutdown();
}

View File

@@ -67,17 +67,15 @@ public class CachingAllWorkdirProvider implements CacheSupportingWorkdirProvider
}
private <R, W> SimpleWorkdirFactory.ParentAndClone<R, W> createNewWorkdir(CreateWorkdirContext<R, W, ?> createWorkdirContext) throws IOException {
String id = createWorkdirContext.getScmRepository().getId();
Stopwatch stopwatch = Stopwatch.createStarted();
File newWorkdir = workdirProvider.createNewWorkdir();
workdirs.put(id, newWorkdir);
SimpleWorkdirFactory.ParentAndClone<R, W> parentAndClone = createWorkdirContext.getInitializer().initialize(newWorkdir);
LOG.debug("initialized new workdir for {} in path {} in {}", createWorkdirContext.getScmRepository().getNamespaceAndName(), newWorkdir, stopwatch.stop());
return parentAndClone;
}
@Override
public void contextClosed(CreateWorkdirContext<?, ?, ?> createWorkdirContext, File workdir) throws IOException {
public void contextClosed(CreateWorkdirContext<?, ?, ?> createWorkdirContext, File workdir) {
String id = createWorkdirContext.getScmRepository().getId();
File putResult = workdirs.putIfAbsent(id, workdir);
if (putResult != null && putResult != workdir) {
@@ -85,7 +83,15 @@ public class CachingAllWorkdirProvider implements CacheSupportingWorkdirProvider
}
}
private void deleteWorkdir(File existingWorkdir) throws IOException {
IOUtil.delete(existingWorkdir, true);
@Override
public void shutdown() {
workdirs.values().parallelStream().forEach(this::deleteWorkdir);
workdirs.clear();
}
private void deleteWorkdir(File existingWorkdir) {
if (existingWorkdir.exists()) {
IOUtil.deleteSilently(existingWorkdir);
}
}
}

View File

@@ -48,4 +48,8 @@ public class NoneCachingWorkdirProvider implements CacheSupportingWorkdirProvide
public void contextClosed(CreateWorkdirContext<?, ?, ?> createWorkdirContext, File workdir) throws IOException {
IOUtil.delete(workdir, true);
}
@Override
public void shutdown() {
}
}

View File

@@ -26,13 +26,17 @@ package sonia.scm.repository.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.io.File;
import java.io.IOException;
public abstract class SimpleWorkdirFactory<R, W, C> implements WorkdirFactory<R, W, C> {
@Extension
public abstract class SimpleWorkdirFactory<R, W, C> implements WorkdirFactory<R, W, C>, ServletContextListener {
private static final Logger LOG = LoggerFactory.getLogger(SimpleWorkdirFactory.class);
@@ -79,6 +83,16 @@ public abstract class SimpleWorkdirFactory<R, W, C> implements WorkdirFactory<R,
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
workdirProvider.shutdown();
}
@Override
public void contextInitialized(ServletContextEvent sce) {
// nothing to do
}
@FunctionalInterface
public interface WorkdirInitializer<R, W> {
ParentAndClone<R, W> initialize(File target) throws IOException;

View File

@@ -358,6 +358,15 @@ public final class IOUtil
delete(file, false);
}
public static void deleteSilently(File file)
{
try {
delete(file, true);
} catch (IOException e) {
// silent delete throws no exception
}
}
/**
* Method description
*

View File

@@ -0,0 +1,124 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.util;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junitpioneer.jupiter.TempDirectory;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.Repository;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith({MockitoExtension.class, TempDirectory.class})
class CachingAllWorkdirProviderTest {
private static final Repository REPOSITORY = new Repository("1", "git", "space", "X");
@Mock
WorkdirProvider workdirProvider;
@InjectMocks
CachingAllWorkdirProvider cachingAllWorkdirProvider;
@Mock
CreateWorkdirContext<Object, Path, Path> createWorkdirContext;
@Mock
SimpleWorkdirFactory.WorkdirInitializer<Object, Path> initializer;
@Mock
SimpleWorkdirFactory.WorkdirReclaimer<Object, Path> reclaimer;
@BeforeEach
void initContext() throws IOException, SimpleWorkdirFactory.ReclaimFailedException {
lenient().when(createWorkdirContext.getInitializer()).thenReturn(initializer);
lenient().when(createWorkdirContext.getReclaimer()).thenReturn(reclaimer);
lenient().when(initializer.initialize(any()))
.thenAnswer(invocationOnMock -> new SimpleWorkdirFactory.ParentAndClone<Object, Object>(null, null, invocationOnMock.getArgument(0, File.class)));
lenient().when(reclaimer.reclaim(any()))
.thenAnswer(invocationOnMock -> new SimpleWorkdirFactory.ParentAndClone<Object, Object>(null, null, invocationOnMock.getArgument(0, File.class)));
}
@Test
void shouldCreateNewWorkdirForTheFirstRequest(@TempDirectory.TempDir Path temp) throws IOException {
when(createWorkdirContext.getScmRepository()).thenReturn(REPOSITORY);
when(workdirProvider.createNewWorkdir()).thenReturn(temp.toFile());
SimpleWorkdirFactory.ParentAndClone<?, ?> workdir = cachingAllWorkdirProvider.getWorkdir(createWorkdirContext);
verify(initializer).initialize(temp.toFile());
}
@Test
void shouldCreateWorkdirOnlyOnceForTheSameRepository(@TempDirectory.TempDir Path temp) throws IOException, SimpleWorkdirFactory.ReclaimFailedException {
when(createWorkdirContext.getScmRepository()).thenReturn(REPOSITORY);
when(workdirProvider.createNewWorkdir()).thenReturn(temp.toFile());
SimpleWorkdirFactory.ParentAndClone<?, ?> firstWorkdir = cachingAllWorkdirProvider.getWorkdir(createWorkdirContext);
cachingAllWorkdirProvider.contextClosed(createWorkdirContext, firstWorkdir.getDirectory());
SimpleWorkdirFactory.ParentAndClone<?, ?> secondWorkdir = cachingAllWorkdirProvider.getWorkdir(createWorkdirContext);
verify(initializer).initialize(temp.toFile());
verify(reclaimer).reclaim(temp.toFile());
assertThat(secondWorkdir.getDirectory()).isEqualTo(temp.toFile());
}
@Test
void shouldCacheOnlyOneWorkdirForRepository(@TempDirectory.TempDir Path temp) throws IOException, SimpleWorkdirFactory.ReclaimFailedException {
when(createWorkdirContext.getScmRepository()).thenReturn(REPOSITORY);
File firstDirectory = temp.resolve("first").toFile();
firstDirectory.mkdirs();
File secondDirectory = temp.resolve("second").toFile();
secondDirectory.mkdirs();
when(workdirProvider.createNewWorkdir()).thenReturn(
firstDirectory,
secondDirectory);
SimpleWorkdirFactory.ParentAndClone<?, ?> firstWorkdir = cachingAllWorkdirProvider.getWorkdir(createWorkdirContext);
SimpleWorkdirFactory.ParentAndClone<?, ?> secondWorkdir = cachingAllWorkdirProvider.getWorkdir(createWorkdirContext);
cachingAllWorkdirProvider.contextClosed(createWorkdirContext, firstWorkdir.getDirectory());
cachingAllWorkdirProvider.contextClosed(createWorkdirContext, secondWorkdir.getDirectory());
verify(reclaimer, never()).reclaim(any());
verify(initializer).initialize(firstDirectory);
verify(initializer).initialize(secondDirectory);
assertThat(firstWorkdir.getDirectory()).isNotEqualTo(secondWorkdir.getDirectory());
assertThat(firstWorkdir.getDirectory()).exists();
assertThat(secondWorkdir.getDirectory()).doesNotExist();
}
}

View File

@@ -32,6 +32,7 @@ import org.junit.rules.TemporaryFolder;
import sonia.scm.repository.Repository;
import sonia.scm.util.IOUtil;
import javax.servlet.ServletContextEvent;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
@@ -72,6 +73,10 @@ public class SimpleWorkdirFactoryTest {
IOUtil.delete(workdir);
}
}
@Override
public void shutdown() {
}
};
simpleWorkdirFactory = new SimpleWorkdirFactory<Closeable, Closeable, Context>(configurableTestWorkdirProvider) {
@Override

View File

@@ -36,6 +36,7 @@ import sonia.scm.web.HgRepositoryEnvironmentBuilder;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.servlet.ServletContextEvent;
import java.io.File;
import java.io.IOException;
import java.util.Map;

View File

@@ -40,6 +40,7 @@ import sonia.scm.repository.util.CacheSupportingWorkdirProvider;
import sonia.scm.repository.util.SimpleWorkdirFactory;
import javax.inject.Inject;
import javax.servlet.ServletContextEvent;
import java.io.File;
import java.io.IOException;