Skip to content

Commit

Permalink
Fix the problem that deletable extensions created by plugins cannot b…
Browse files Browse the repository at this point in the history
…e recycled (#4526)

#### What type of PR is this?

/kind bug
/area core
/milestone 2.9.x

#### What this PR does / why we need it:

As I mentioned in <#4519>, some extensions which are deletable cannot be recycled by GC. This PR provides an ability to watch scheme changes and recycles deletable extensions.

#### Which issue(s) this PR fixes:

Fixes #4519

#### Does this PR introduce a user-facing change?

```release-note
修复因重启后部分可被回收的资源一直处于删除中的状态
```
  • Loading branch information
JohnNiang authored Aug 31, 2023
1 parent 329b389 commit 58eac2e
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.ExtensionConverter;
import run.halo.app.extension.SchemeManager;
import run.halo.app.extension.SchemeWatcherManager;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.DefaultController;
Expand All @@ -29,12 +30,18 @@ class GcReconciler implements Reconciler<GcRequest> {

private final SchemeManager schemeManager;

GcReconciler(ExtensionClient client, ExtensionStoreClient storeClient,
ExtensionConverter converter, SchemeManager schemeManager) {
private final SchemeWatcherManager schemeWatcherManager;

GcReconciler(ExtensionClient client,
ExtensionStoreClient storeClient,
ExtensionConverter converter,
SchemeManager schemeManager,
SchemeWatcherManager schemeWatcherManager) {
this.client = client;
this.storeClient = storeClient;
this.converter = converter;
this.schemeManager = schemeManager;
this.schemeWatcherManager = schemeWatcherManager;
}


Expand All @@ -56,7 +63,7 @@ public Result reconcile(GcRequest request) {
@Override
public Controller setupWith(ControllerBuilder builder) {
var queue = new DefaultQueue<GcRequest>(Instant::now, Duration.ofMillis(500));
var synchronizer = new GcSynchronizer(client, queue, schemeManager);
var synchronizer = new GcSynchronizer(client, queue, schemeManager, schemeWatcherManager);
return new DefaultController<>(
"garbage-collector-controller",
this,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package run.halo.app.extension.gc;

import static run.halo.app.extension.Comparators.compareCreationTimestamp;

import java.util.function.Predicate;
import run.halo.app.extension.Extension;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.Scheme;
import run.halo.app.extension.SchemeManager;
import run.halo.app.extension.SchemeWatcherManager;
import run.halo.app.extension.SchemeWatcherManager.SchemeRegistered;
import run.halo.app.extension.Watcher;
import run.halo.app.extension.controller.RequestQueue;
import run.halo.app.extension.controller.Synchronizer;
Expand All @@ -13,22 +17,22 @@ class GcSynchronizer implements Synchronizer<GcRequest> {

private final ExtensionClient client;

private final RequestQueue<GcRequest> queue;

private final SchemeManager schemeManager;

private final SchemeWatcherManager schemeWatcherManager;

private boolean disposed = false;

private boolean started = false;

private final Watcher watcher;

GcSynchronizer(ExtensionClient client, RequestQueue<GcRequest> queue,
SchemeManager schemeManager) {
SchemeManager schemeManager, SchemeWatcherManager schemeWatcherManager) {
this.client = client;
this.queue = queue;
this.schemeManager = schemeManager;
this.watcher = new GcWatcher(queue);
this.schemeWatcherManager = schemeWatcherManager;
}

@Override
Expand All @@ -51,10 +55,17 @@ public void start() {
return;
}
this.started = true;
this.schemeWatcherManager.register(event -> {
if (event instanceof SchemeRegistered registeredEvent) {
var newScheme = registeredEvent.getNewScheme();
client.list(newScheme.type(), deleted(), compareCreationTimestamp(true))
.forEach(watcher::onDelete);
}
});
client.watch(watcher);
schemeManager.schemes().stream()
.map(Scheme::type)
.forEach(type -> client.list(type, deleted(), null)
.forEach(type -> client.list(type, deleted(), compareCreationTimestamp(true))
.forEach(watcher::onDelete));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package run.halo.app.extension.gc;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.verify;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.SchemeManager;
import run.halo.app.extension.SchemeWatcherManager;
import run.halo.app.extension.SchemeWatcherManager.SchemeWatcher;

@ExtendWith(MockitoExtension.class)
class GcSynchronizerTest {

@Mock
ExtensionClient client;

@Mock
SchemeManager schemeManager;

@Mock
SchemeWatcherManager schemeWatcherManager;

@InjectMocks
GcSynchronizer synchronizer;

@Test
void shouldStartNormally() {
synchronizer.start();

assertFalse(synchronizer.isDisposed());
verify(schemeWatcherManager).register(any(SchemeWatcher.class));
verify(client).watch(isA(GcWatcher.class));
verify(schemeManager).schemes();
}

@Test
void shouldDisposeSuccessfully() {
assertFalse(synchronizer.isDisposed());

synchronizer.dispose();

assertTrue(synchronizer.isDisposed());
}
}

0 comments on commit 58eac2e

Please sign in to comment.