feat: 1.3.0
1. 优化手动重试模块
This commit is contained in:
parent
0e6d43681a
commit
ff42df5229
@ -1,5 +1,6 @@
|
||||
package com.aizuda.easy.retry.client.core.intercepter;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.aizuda.easy.retry.common.core.constant.SystemConstants;
|
||||
import com.aizuda.easy.retry.common.core.model.EasyRetryHeaders;
|
||||
import lombok.Getter;
|
||||
@ -65,6 +66,10 @@ public class RetrySiteSnapshot {
|
||||
}
|
||||
|
||||
public static boolean isMethodEntrance(String methodEntrance) {
|
||||
if (StrUtil.isBlank(getMethodEntrance())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getMethodEntrance().equals(methodEntrance);
|
||||
}
|
||||
|
||||
@ -109,7 +114,8 @@ public class RetrySiteSnapshot {
|
||||
}
|
||||
|
||||
public static boolean isRetryForStatusCode() {
|
||||
return Objects.nonNull(getRetryStatusCode()) && getRetryStatusCode().equals(SystemConstants.EASY_RETRY_STATUS_CODE);
|
||||
return Objects.nonNull(getRetryStatusCode()) && getRetryStatusCode()
|
||||
.equals(SystemConstants.EASY_RETRY_STATUS_CODE);
|
||||
}
|
||||
|
||||
public static Long getEntryMethodTime() {
|
||||
@ -124,11 +130,11 @@ public class RetrySiteSnapshot {
|
||||
ENTRY_METHOD_TIME.remove();
|
||||
}
|
||||
|
||||
public static void removeRetryHeader(){
|
||||
public static void removeRetryHeader() {
|
||||
RETRY_HEADER.remove();
|
||||
}
|
||||
|
||||
public static void removeRetryStatusCode(){
|
||||
public static void removeRetryStatusCode() {
|
||||
RETRY_STATUS_CODE.remove();
|
||||
}
|
||||
|
||||
@ -166,6 +172,11 @@ public class RetrySiteSnapshot {
|
||||
* 远程重试阶段
|
||||
*/
|
||||
REMOTE(2),
|
||||
|
||||
/**
|
||||
* 手动提交数据
|
||||
*/
|
||||
MANUAL_REPORT(3),
|
||||
;
|
||||
|
||||
private final int stage;
|
||||
|
@ -1,8 +1,13 @@
|
||||
package com.aizuda.easy.retry.client.core.retryer;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.aizuda.easy.retry.client.core.RetryOperations;
|
||||
import com.aizuda.easy.retry.client.core.intercepter.RetrySiteSnapshot;
|
||||
import com.aizuda.easy.retry.client.core.strategy.ExecutorMethod;
|
||||
import com.aizuda.easy.retry.client.core.strategy.RetryStrategy;
|
||||
import com.aizuda.easy.retry.common.core.model.EasyRetryHeaders;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 手动生成重试任务模板类
|
||||
@ -20,7 +25,20 @@ public class EasyRetryTemplate implements RetryOperations {
|
||||
|
||||
@Override
|
||||
public void executeRetry() {
|
||||
retryStrategy.openRetry(scene, executorMethodClass.getName(), params);
|
||||
|
||||
Integer stage = RetrySiteSnapshot.getStage();
|
||||
try {
|
||||
retryStrategy.openRetry(scene, executorMethodClass.getName(), params);
|
||||
} finally {
|
||||
// stage == null 则非嵌套重试, 需求清除线程记录的数据信息
|
||||
// stage != null 则由上层调度的进行清除线程记录的数据信息
|
||||
if (Objects.isNull(stage)) {
|
||||
RetrySiteSnapshot.removeAll();
|
||||
} else {
|
||||
// 还原原始的重试阶段
|
||||
RetrySiteSnapshot.setStage(stage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setExecutorMethodClass(
|
||||
|
@ -2,6 +2,7 @@ package com.aizuda.easy.retry.client.core.retryer;
|
||||
|
||||
import com.aizuda.easy.retry.client.core.strategy.ExecutorMethod;
|
||||
import com.aizuda.easy.retry.client.core.strategy.LocalRetryStrategies;
|
||||
import com.aizuda.easy.retry.client.core.strategy.ManualRetryStrategies;
|
||||
import com.aizuda.easy.retry.client.core.strategy.RetryStrategy;
|
||||
import com.aizuda.easy.retry.common.core.context.SpringContext;
|
||||
|
||||
@ -41,7 +42,7 @@ public class RetryTaskTemplateBuilder {
|
||||
easyRetryTemplate.setParams(params);
|
||||
easyRetryTemplate.setExecutorMethodClass(executorMethodClass);
|
||||
easyRetryTemplate.setScene(scene);
|
||||
RetryStrategy retryStrategy = SpringContext.getBeanByType(LocalRetryStrategies.class);
|
||||
RetryStrategy retryStrategy = SpringContext.getBeanByType(ManualRetryStrategies.class);
|
||||
easyRetryTemplate.setRetryStrategy(retryStrategy);
|
||||
return easyRetryTemplate;
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ import java.util.function.Consumer;
|
||||
public abstract class AbstractRetryStrategies implements RetryStrategy {
|
||||
|
||||
@Autowired
|
||||
private List<EasyRetryListener> EasyRetryListeners;
|
||||
private List<EasyRetryListener> easyRetryListeners;
|
||||
|
||||
@Override
|
||||
public RetryerResultContext openRetry(String sceneName, String executorClassName, Object[] params) {
|
||||
@ -52,7 +52,7 @@ public abstract class AbstractRetryStrategies implements RetryStrategy {
|
||||
retryerResultContext.setRetryerInfo(retryerInfo);
|
||||
|
||||
try {
|
||||
for (EasyRetryListener EasyRetryListener : EasyRetryListeners) {
|
||||
for (EasyRetryListener EasyRetryListener : easyRetryListeners) {
|
||||
EasyRetryListener.beforeRetry(sceneName, executorClassName, params);
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ public abstract class AbstractRetryStrategies implements RetryStrategy {
|
||||
Object result = retryerResultContext.getResult();
|
||||
RetryerInfo retryerInfo = retryerResultContext.getRetryerInfo();
|
||||
|
||||
for (EasyRetryListener EasyRetryListener : EasyRetryListeners) {
|
||||
for (EasyRetryListener EasyRetryListener : easyRetryListeners) {
|
||||
EasyRetryListener.successOnRetry(result, retryerInfo.getScene(), retryerInfo.getExecutorClassName());
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ public abstract class AbstractRetryStrategies implements RetryStrategy {
|
||||
|
||||
RetryerInfo retryerInfo = context.getRetryerInfo();
|
||||
try {
|
||||
for (EasyRetryListener EasyRetryListener : EasyRetryListeners) {
|
||||
for (EasyRetryListener EasyRetryListener : easyRetryListeners) {
|
||||
EasyRetryListener
|
||||
.failureOnRetry(retryerInfo.getScene(), retryerInfo.getExecutorClassName(), throwable);
|
||||
}
|
||||
|
@ -5,21 +5,21 @@ import com.aizuda.easy.retry.client.core.RetryExecutorParameter;
|
||||
import com.aizuda.easy.retry.client.core.exception.EasyRetryClientException;
|
||||
import com.aizuda.easy.retry.client.core.intercepter.RetrySiteSnapshot;
|
||||
import com.aizuda.easy.retry.client.core.report.ReportHandler;
|
||||
import com.github.rholder.retry.*;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.aizuda.easy.retry.client.core.retryer.RetryType;
|
||||
import com.aizuda.easy.retry.client.core.retryer.RetryerInfo;
|
||||
import com.aizuda.easy.retry.client.core.retryer.RetryerResultContext;
|
||||
import com.aizuda.easy.retry.common.core.enums.RetryResultStatusEnum;
|
||||
import com.aizuda.easy.retry.common.core.log.LogUtils;
|
||||
import com.github.rholder.retry.Attempt;
|
||||
import com.github.rholder.retry.RetryListener;
|
||||
import com.github.rholder.retry.StopStrategies;
|
||||
import com.github.rholder.retry.StopStrategy;
|
||||
import com.github.rholder.retry.WaitStrategies;
|
||||
import com.github.rholder.retry.WaitStrategy;
|
||||
import com.google.common.base.Predicate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -96,7 +96,7 @@ public class LocalRetryStrategies extends AbstractRetryStrategies {
|
||||
if (RetryType.LOCAL_REMOTE.name().equals(retryerInfo.getRetryType().name())){
|
||||
// 上报
|
||||
log.debug("上报 scene:[{}]", retryerInfo.getScene());
|
||||
reportHandler.asyncReport(retryerInfo.getScene(), retryerInfo.getExecutorClassName(), params);
|
||||
doReport(retryerInfo, params);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -115,21 +115,7 @@ public class LocalRetryStrategies extends AbstractRetryStrategies {
|
||||
// 仅仅是远程重试则直接上报
|
||||
log.debug("上报 scene:[{}]", retryerInfo.getScene());
|
||||
return () -> {
|
||||
|
||||
if (retryerInfo.isAsync()) {
|
||||
if (retryerInfo.isForceReport()) {
|
||||
reportHandler.asyncReportWithForce(retryerInfo.getScene(), retryerInfo.getExecutorClassName(), params);
|
||||
} else {
|
||||
reportHandler.asyncReport(retryerInfo.getScene(), retryerInfo.getExecutorClassName(), params);
|
||||
}
|
||||
} else {
|
||||
if (retryerInfo.isForceReport()) {
|
||||
reportHandler.syncReport(retryerInfo.getScene(), retryerInfo.getExecutorClassName(), params, retryerInfo.getTimeout(), retryerInfo.getUnit());
|
||||
} else {
|
||||
reportHandler.syncReport(retryerInfo.getScene(), retryerInfo.getExecutorClassName(), params, retryerInfo.getTimeout(), retryerInfo.getUnit());
|
||||
}
|
||||
}
|
||||
|
||||
doReport(retryerInfo, params);
|
||||
RetrySiteSnapshot.setStage(RetrySiteSnapshot.EnumStage.REMOTE.getStage());
|
||||
return null;
|
||||
};
|
||||
@ -139,6 +125,31 @@ public class LocalRetryStrategies extends AbstractRetryStrategies {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报数据
|
||||
*
|
||||
* @param retryerInfo 定义重试场景的信息
|
||||
* @param params 执行参数
|
||||
*/
|
||||
private void doReport(final RetryerInfo retryerInfo, final Object[] params) {
|
||||
|
||||
if (retryerInfo.isAsync()) {
|
||||
if (retryerInfo.isForceReport()) {
|
||||
reportHandler.asyncReportWithForce(retryerInfo.getScene(), retryerInfo.getExecutorClassName(), params);
|
||||
} else {
|
||||
reportHandler.asyncReport(retryerInfo.getScene(), retryerInfo.getExecutorClassName(), params);
|
||||
}
|
||||
} else {
|
||||
if (retryerInfo.isForceReport()) {
|
||||
reportHandler.syncReport(retryerInfo.getScene(), retryerInfo.getExecutorClassName(),
|
||||
params, retryerInfo.getTimeout(), retryerInfo.getUnit());
|
||||
} else {
|
||||
reportHandler.syncReport(retryerInfo.getScene(), retryerInfo.getExecutorClassName(),
|
||||
params, retryerInfo.getTimeout(), retryerInfo.getUnit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RetryExecutorParameter<WaitStrategy, StopStrategy> getRetryExecutorParameter(RetryerInfo retryerInfo) {
|
||||
|
||||
|
@ -0,0 +1,166 @@
|
||||
package com.aizuda.easy.retry.client.core.strategy;
|
||||
|
||||
import com.aizuda.easy.retry.client.core.RetryExecutor;
|
||||
import com.aizuda.easy.retry.client.core.RetryExecutorParameter;
|
||||
import com.aizuda.easy.retry.client.core.intercepter.RetrySiteSnapshot;
|
||||
import com.aizuda.easy.retry.client.core.report.ReportHandler;
|
||||
import com.aizuda.easy.retry.client.core.retryer.RetryType;
|
||||
import com.aizuda.easy.retry.client.core.retryer.RetryerInfo;
|
||||
import com.aizuda.easy.retry.client.core.retryer.RetryerResultContext;
|
||||
import com.aizuda.easy.retry.common.core.enums.RetryResultStatusEnum;
|
||||
import com.aizuda.easy.retry.common.core.log.LogUtils;
|
||||
import com.github.rholder.retry.Attempt;
|
||||
import com.github.rholder.retry.RetryListener;
|
||||
import com.github.rholder.retry.StopStrategies;
|
||||
import com.github.rholder.retry.StopStrategy;
|
||||
import com.github.rholder.retry.WaitStrategies;
|
||||
import com.github.rholder.retry.WaitStrategy;
|
||||
import com.google.common.base.Predicate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author: www.byteblogs.com
|
||||
* @date : 2023-05-15 18:19
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ManualRetryStrategies extends AbstractRetryStrategies {
|
||||
|
||||
@Autowired
|
||||
private ReportHandler reportHandler;
|
||||
|
||||
@Override
|
||||
protected void setStage() {
|
||||
RetrySiteSnapshot.setStage(RetrySiteSnapshot.EnumStage.MANUAL_REPORT.getStage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Consumer<Object> doRetrySuccessConsumer(final RetryerResultContext context) {
|
||||
return o -> {
|
||||
LogUtils.debug(log, "ManualRetryStrategies doRetrySuccessConsumer ");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(final RetryerResultContext context) {
|
||||
context.setRetryResultStatusEnum(RetryResultStatusEnum.FAILURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean preValidator(final RetryerInfo retryerInfo, final RetryerResultContext resultContext) {
|
||||
|
||||
if (retryerInfo.isForceReport()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (RetrySiteSnapshot.isRunning()) {
|
||||
resultContext.setRetryResultStatusEnum(RetryResultStatusEnum.FAILURE);
|
||||
resultContext.setMessage("执行重试检验不通过 原因: 存在正在运行的重试任务");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unexpectedError(final Exception e, final RetryerResultContext retryerResultContext) {
|
||||
retryerResultContext.setRetryResultStatusEnum(RetryResultStatusEnum.FAILURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void success(final RetryerResultContext retryerResultContext) {
|
||||
retryerResultContext.setRetryResultStatusEnum(RetryResultStatusEnum.SUCCESS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Consumer<Throwable> doGetRetryErrorConsumer(final RetryerInfo retryerInfo, final Object[] params) {
|
||||
return throwable -> {
|
||||
LogUtils.debug(log, "ManualRetryStrategies doGetRetryErrorConsumer ");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Callable doGetCallable(final RetryExecutor<WaitStrategy, StopStrategy> retryExecutor, Object[] params) {
|
||||
RetryerInfo retryerInfo = retryExecutor.getRetryerInfo();
|
||||
return () -> doReport(retryerInfo, params);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报数据
|
||||
*
|
||||
* @param retryerInfo 定义重试场景的信息
|
||||
* @param params 执行参数
|
||||
*/
|
||||
private boolean doReport(final RetryerInfo retryerInfo, final Object[] params) {
|
||||
|
||||
if (retryerInfo.isAsync()) {
|
||||
if (retryerInfo.isForceReport()) {
|
||||
return reportHandler
|
||||
.asyncReportWithForce(retryerInfo.getScene(), retryerInfo.getExecutorClassName(), params);
|
||||
} else {
|
||||
return reportHandler.asyncReport(retryerInfo.getScene(), retryerInfo.getExecutorClassName(), params);
|
||||
}
|
||||
} else {
|
||||
if (retryerInfo.isForceReport()) {
|
||||
return reportHandler.syncReport(retryerInfo.getScene(), retryerInfo.getExecutorClassName(),
|
||||
params, retryerInfo.getTimeout(), retryerInfo.getUnit());
|
||||
} else {
|
||||
return reportHandler.syncReport(retryerInfo.getScene(), retryerInfo.getExecutorClassName(),
|
||||
params, retryerInfo.getTimeout(), retryerInfo.getUnit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RetryExecutorParameter<WaitStrategy, StopStrategy> getRetryExecutorParameter(RetryerInfo retryerInfo) {
|
||||
return new RetryExecutorParameter<WaitStrategy, StopStrategy>() {
|
||||
|
||||
@Override
|
||||
public Predicate<Throwable> exceptionPredicate() {
|
||||
return throwable -> ManualRetryStrategies.super.validate(throwable.getClass(), retryerInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaitStrategy backOff() {
|
||||
return WaitStrategies.fixedWait(500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StopStrategy stop() {
|
||||
return StopStrategies.stopAfterAttempt(5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RetryListener> getRetryListeners() {
|
||||
return Collections.singletonList(new RetryListener() {
|
||||
@Override
|
||||
public <V> void onRetry(Attempt<V> attempt) {
|
||||
if (attempt.hasResult()) {
|
||||
LogUtils.error(log, "easy-retry 手动创建重试数据成功,第[{}]次调度", attempt.getAttemptNumber());
|
||||
}
|
||||
|
||||
if (attempt.hasException()) {
|
||||
LogUtils.error(log, "easy-retry 手动创建重试数据失败,第[{}]次调度 ", attempt.getAttemptNumber(),
|
||||
attempt.getExceptionCause());
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(final int stage, final RetryType retryType) {
|
||||
return RetrySiteSnapshot.EnumStage.MANUAL_REPORT.getStage() == stage;
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit;
|
||||
* @date : 2022-03-07 14:07
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@ExecutorMethodRegister(scene = CustomSyncCreateTask.SCENE, async = false, timeout = 10000, unit = TimeUnit.MILLISECONDS)
|
||||
@ExecutorMethodRegister(scene = CustomSyncCreateTask.SCENE, async = false, timeout = 10000, unit = TimeUnit.MILLISECONDS, forceReport = false)
|
||||
@Slf4j
|
||||
public class CustomSyncCreateTask implements ExecutorMethod {
|
||||
|
||||
|
@ -1,10 +1,16 @@
|
||||
package com.example.demo;
|
||||
|
||||
import com.aizuda.easy.retry.client.core.annotation.Retryable;
|
||||
import com.aizuda.easy.retry.client.core.exception.EasyRetryClientException;
|
||||
import com.aizuda.easy.retry.client.core.retryer.EasyRetryTemplate;
|
||||
import com.aizuda.easy.retry.client.core.retryer.RetryTaskTemplateBuilder;
|
||||
import com.example.model.Zoo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@ -22,4 +28,25 @@ public class NestMethodService {
|
||||
public void testNestMethod() {
|
||||
testExistsTransactionalRetryService.testSimpleInsert(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
@Retryable(scene = "testNestMethodForCustomSyncCreateTask" , isThrowException = false)
|
||||
@Transactional
|
||||
public void testNestMethodForCustomSyncCreateTask() {
|
||||
|
||||
Random random = new Random();
|
||||
int i = random.nextInt(5);
|
||||
if (i <= 2) {
|
||||
throw new EasyRetryClientException("测试注解重试和手动重试");
|
||||
}
|
||||
|
||||
Zoo zoo = new Zoo();
|
||||
zoo.setNow(LocalDateTime.now());
|
||||
EasyRetryTemplate retryTemplate = RetryTaskTemplateBuilder.newBuilder()
|
||||
.withExecutorMethod(CustomSyncCreateTask.class)
|
||||
.withParam(zoo)
|
||||
.withScene(CustomSyncCreateTask.SCENE)
|
||||
.build();
|
||||
|
||||
retryTemplate.executeRetry();
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,12 @@ import com.aizuda.easy.retry.client.core.retryer.EasyRetryTemplate;
|
||||
import com.aizuda.easy.retry.client.core.retryer.RetryTaskTemplateBuilder;
|
||||
import com.example.demo.CustomAsyncCreateTask;
|
||||
import com.example.demo.CustomSyncCreateTask;
|
||||
import com.example.demo.NestMethodService;
|
||||
import com.example.model.Cat;
|
||||
import com.example.model.Zoo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@ -20,6 +22,9 @@ import java.time.LocalDateTime;
|
||||
@Slf4j
|
||||
public class EasyRetryTemplateTest {
|
||||
|
||||
@Autowired
|
||||
private NestMethodService nestMethodService;
|
||||
|
||||
@Test
|
||||
public void generateAsyncTaskTest() throws InterruptedException {
|
||||
|
||||
@ -52,4 +57,12 @@ public class EasyRetryTemplateTest {
|
||||
retryTemplate.executeRetry();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestMethodForCustomSyncCreateTask() throws InterruptedException {
|
||||
nestMethodService.testNestMethodForCustomSyncCreateTask();
|
||||
|
||||
Thread.sleep(90000);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user