package org.zstack.core.gc;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.zstack.core.Platform;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.EventFacade;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SQL;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.core.thread.AsyncThread;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.utils.FieldUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.gson.JSONObjectUtil;
import org.zstack.utils.logging.CLogger;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created by xing5 on 2017/3/3.
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public abstract class GarbageCollector {
static final CLogger logger = Utils.getLogger(GarbageCollector.class);
@Autowired
protected DatabaseFacade dbf;
@Autowired
protected CloudBus bus;
@Autowired
protected ErrorFacade errf;
@Autowired
protected EventFacade evtf;
@Autowired
GarbageCollectorManagerImpl gcMgr;
Runnable canceller;
private AtomicBoolean lockJob = new AtomicBoolean(false);
// override in the subclass to give a name to the GC job
public String NAME = getClass().getName();
public int EXECUTED_TIMES;
String uuid;
public String getUuid() {
return uuid;
}
protected abstract void triggerNow(GCCompletion completion);
protected boolean lock() {
return lockJob.compareAndSet(false, true);
}
protected void unlock() {
lockJob.set(false);
}
protected void success() {
assert uuid != null;
assert canceller != null;
logger.debug(String.format("[GC] a job[name:%s, id:%s] completes successfully", NAME, uuid));
canceller.run();
SQL.New(GarbageCollectorVO.class)
.eq(GarbageCollectorVO_.uuid, uuid)
.set(GarbageCollectorVO_.status, GCStatus.Done).update();
gcMgr.deregisterGC(this);
}
protected void cancel() {
assert uuid != null;
assert canceller != null;
logger.debug(String.format("[GC] a job[name:%s, id:%s] is cancelled by itself", NAME, uuid));
canceller.run();
SQL.New(GarbageCollectorVO.class)
.eq(GarbageCollectorVO_.uuid, uuid)
.set(GarbageCollectorVO_.status, GCStatus.Done).update();
gcMgr.deregisterGC(this);
}
protected void fail(ErrorCode err) {
assert uuid != null;
unlock();
logger.debug(String.format("[GC] a job[name:%s, id:%s] failed because %s", NAME, uuid,err));
GarbageCollectorVO vo = dbf.findByUuid(uuid, GarbageCollectorVO.class);
if (vo == null) {
logger.warn(String.format("[GC] cannot find a job[name:%s, id:%s], assume it's deleted", NAME, uuid));
cancel();
return;
}
vo.setStatus(GCStatus.Idle);
dbf.update(vo);
}
final protected void saveToDb() {
Map context = new HashMap<>();
for (Field f : FieldUtils.getAllFields(getClass())) {
if (!f.isAnnotationPresent(GC.class)) {
continue;
}
try {
f.setAccessible(true);
context.put(f.getName(), f.get(this));
} catch (IllegalAccessException e) {
throw new CloudRuntimeException(e);
}
}
FieldUtils.getAllFields(getClass()).forEach(f -> {
});
GarbageCollectorVO vo = new GarbageCollectorVO();
vo.setUuid(Platform.getUuid());
vo.setContext(JSONObjectUtil.toJsonString(context));
vo.setRunnerClass(getClass().getName());
vo.setManagementNodeUuid(Platform.getManagementServerId());
vo.setStatus(GCStatus.Idle);
if (this instanceof EventBasedGarbageCollector) {
vo.setType(GarbageCollectorType.EventBased.toString());
} else {
vo.setType(GarbageCollectorType.TimeBased.toString());
}
vo.setName(NAME);
vo = dbf.persistAndRefresh(vo);
uuid = vo.getUuid();
logger.debug(String.format("[GC] saved a job[name:%s, id:%s] to DB", NAME, uuid));
}
void loadFromVO(GarbageCollectorVO vo) {
Object dataObj = JSONObjectUtil.toObject(vo.getContext(), getClass());
for (Field f : FieldUtils.getAllFields(getClass())) {
if (!f.isAnnotationPresent(GC.class)) {
continue;
}
try {
f.setAccessible(true);
f.set(this, f.get(dataObj));
} catch (Exception e) {
throw new CloudRuntimeException(e);
}
}
uuid = vo.getUuid();
vo.setStatus(GCStatus.Idle);
vo.setManagementNodeUuid(Platform.getManagementServerId());
dbf.update(vo);
gcMgr.registerGC(this);
}
@AsyncThread
void runTrigger() {
GarbageCollector self = this;
EXECUTED_TIMES++;
try {
triggerNow(new GCCompletion(null) {
@Override
public void cancel() {
self.cancel();
}
@Override
public void success() {
self.success();
}
@Override
public void fail(ErrorCode errorCode) {
self.fail(errorCode);
}
});
} catch (Throwable t) {
logger.warn(String.format("[GC] unhandled exception happened when" +
" running a GC job[name:%s, id:%s]", NAME, uuid), t);
fail(errf.stringToInternalError(t.getMessage()));
}
}
}