mirror of https://github.com/halo-dev/halo
Support restarting Halo (#4361)
#### What type of PR is this? /kind feature /area core /milestone 2.9.x #### What this PR does / why we need it: Support restarting Halo and enable restart endpoint by default. Restart endpoint detail: request uri: `/actuator/restart` request method: `POST` Please note that memory usage may slightly increase after restarting Halo. #### Does this PR introduce a user-facing change? ```release-note 支持在线重启 Halo。 ```pull/4306/head^2
parent
b9b663e124
commit
1172f4a98c
|
@ -52,8 +52,6 @@ dependencies {
|
|||
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
|
||||
annotationProcessor "org.springframework:spring-context-indexer"
|
||||
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
testImplementation 'io.projectreactor:reactor-test'
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package run.halo.app.actuator;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.Application;
|
||||
|
||||
@WebEndpoint(id = "restart")
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RestartEndpoint implements ApplicationListener<ApplicationStartedEvent> {
|
||||
|
||||
private SpringApplication application;
|
||||
|
||||
private String[] args;
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@WriteOperation
|
||||
public Object restart() {
|
||||
var threadGroup = new ThreadGroup("RestartGroup");
|
||||
var thread = new Thread(threadGroup, this::doRestart, "restartMain");
|
||||
thread.setDaemon(false);
|
||||
thread.setContextClassLoader(Application.class.getClassLoader());
|
||||
thread.start();
|
||||
return Collections.singletonMap("message", "Restarting");
|
||||
}
|
||||
|
||||
private synchronized void doRestart() {
|
||||
log.info("Restarting...");
|
||||
if (this.context != null) {
|
||||
try {
|
||||
closeRecursively(this.context);
|
||||
var shutdownHandlers = SpringApplication.getShutdownHandlers();
|
||||
if (shutdownHandlers instanceof Runnable runnable) {
|
||||
// clear closedContext in org.springframework.boot.SpringApplicationShutdownHook
|
||||
runnable.run();
|
||||
}
|
||||
this.context = this.application.run(args);
|
||||
log.info("Restarted");
|
||||
} catch (Throwable t) {
|
||||
log.error("Failed to restart.", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeRecursively(ApplicationContext ctx) {
|
||||
while (ctx != null) {
|
||||
if (ctx instanceof Closeable closeable) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (IOException e) {
|
||||
log.error("Cannot close context: {}", ctx.getId(), e);
|
||||
}
|
||||
}
|
||||
ctx = ctx.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationStartedEvent event) {
|
||||
if (this.context == null) {
|
||||
this.application = event.getSpringApplication();
|
||||
this.args = event.getArgs();
|
||||
this.context = event.getApplicationContext();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,7 +64,7 @@ management:
|
|||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: ["health", "info", "startup", "globalinfo", "logfile", "shutdown"]
|
||||
include: ["health", "info", "startup", "globalinfo", "logfile", "shutdown", "restart"]
|
||||
endpoint:
|
||||
shutdown:
|
||||
enabled: true
|
||||
|
|
|
@ -1003,9 +1003,9 @@ core:
|
|||
description: Are you sure you want to delete the backup?
|
||||
restore:
|
||||
title: Restore successfully
|
||||
description: After successful restore, you need to restart Halo to load the system resources normally. After clicking OK, we will automatically stop running Halo.
|
||||
shutdown:
|
||||
toast_success: Requested to shutdown operation
|
||||
description: After successful restore, you need to restart Halo to load the system resources normally. After clicking OK, we will automatically restart Halo.
|
||||
restart:
|
||||
toast_success: Requested to restart
|
||||
list:
|
||||
phases:
|
||||
pending: Pending
|
||||
|
@ -1018,7 +1018,7 @@ core:
|
|||
tips:
|
||||
first: 1. The restore process may last for a long time, please do not refresh the page during this period.
|
||||
second: 2. During the restore process, although the existing data will not be cleaned up, if there is a conflict, the data will be overwritten.
|
||||
third: 3. After the restore is completed, you will be prompted to stop running Halo, and you may need to run it manually after stopping.
|
||||
third: 3. After the restore is completed, you need to restart Halo to load the system resources normally.
|
||||
complete: Restore completed, waiting for restart...
|
||||
start: Start restore
|
||||
exception:
|
||||
|
|
|
@ -1003,9 +1003,9 @@ core:
|
|||
description: 确定要删除该备份吗?
|
||||
restore:
|
||||
title: 恢复成功
|
||||
description: 恢复成功之后,需要重启一下 Halo 才能够正常加载系统资源,点击确定之后我们会自动停止运行 Halo
|
||||
shutdown:
|
||||
toast_success: 已请求停止运行
|
||||
description: 恢复成功之后,需要重启一下 Halo 才能够正常加载系统资源,点击确定之后我们会自动重启 Halo。
|
||||
restart:
|
||||
toast_success: 已请求重启
|
||||
list:
|
||||
phases:
|
||||
pending: 准备中
|
||||
|
@ -1018,7 +1018,7 @@ core:
|
|||
tips:
|
||||
first: 1. 恢复过程可能会持续较长时间,期间请勿刷新页面。
|
||||
second: 2. 在恢复的过程中,虽然已有的数据不会被清理掉,但如果有冲突的数据将被覆盖。
|
||||
third: 3. 恢复完成之后会提示停止运行 Halo,停止之后可能需要手动运行。
|
||||
third: 3. 恢复完成之后需要重启 Halo 才能够正常加载系统资源。
|
||||
complete: 恢复完成,等待重启...
|
||||
start: 开始恢复
|
||||
exception:
|
||||
|
|
|
@ -1003,9 +1003,9 @@ core:
|
|||
description: 確定要刪除此備份嗎?
|
||||
restore:
|
||||
title: 還原成功
|
||||
description: 還原成功後,需要重新啟動 Halo 才能正常載入系統資源,點擊確定後我們會自動停止運行 Halo。
|
||||
shutdown:
|
||||
toast_success: 已請求停止運行
|
||||
description: 還原成功後,需要重新啟動 Halo 才能正常載入系統資源,點擊確定之後,我們會自動重啟 Halo。
|
||||
restart:
|
||||
toast_success: 已請求重啟
|
||||
list:
|
||||
phases:
|
||||
pending: 準備中
|
||||
|
@ -1018,7 +1018,7 @@ core:
|
|||
tips:
|
||||
first: 1. 還原過程可能需要較長時間,期間請勿重新整理頁面。
|
||||
second: 2. 在還原過程中,雖然已有的資料不會被清除,但若有衝突的資料將被覆蓋。
|
||||
third: 3. 還原完成後會提示停止運行 Halo,停止後可能需要手動啟動。
|
||||
third: 3. 還原完成後需要重新啟動 Halo 才能正常載入系統資源。
|
||||
complete: 恢復完成,等待重啟...
|
||||
start: 開始還原
|
||||
exception:
|
||||
|
|
|
@ -25,8 +25,8 @@ const onUploaded = () => {
|
|||
};
|
||||
|
||||
async function handleShutdown() {
|
||||
await axios.post(`/actuator/shutdown`);
|
||||
Toast.success(t("core.backup.operations.shutdown.toast_success"));
|
||||
await axios.post(`/actuator/restart`);
|
||||
Toast.success(t("core.backup.operations.restart.toast_success"));
|
||||
|
||||
setTimeout(() => {
|
||||
complete.value = true;
|
||||
|
|
Loading…
Reference in New Issue