default state service: refactor async DB call with Futures transformAsync
This commit is contained in:
parent
ff54d190c9
commit
254c87e29f
@ -22,6 +22,7 @@ import com.google.common.util.concurrent.Futures;
|
|||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
|
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -61,6 +62,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
|
|||||||
import org.thingsboard.server.service.queue.TbClusterService;
|
import org.thingsboard.server.service.queue.TbClusterService;
|
||||||
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService
|
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.annotation.PreDestroy;
|
import javax.annotation.PreDestroy;
|
||||||
@ -401,9 +403,9 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Void apply(@Nullable DeviceStateData state) {
|
public Void apply(@Nullable DeviceStateData state) {
|
||||||
log.debug("[{}][{}] Fetched state from DB [{}]", device.getName(), device.getId(), state);
|
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
addDeviceUsingState(tpi, state);
|
addDeviceUsingState(tpi, state);
|
||||||
|
//updateState(device.getId()); //TODO update activity or inactivity state
|
||||||
} else {
|
} else {
|
||||||
log.warn("{}][{}] Fetched null state from DB", device.getName(), device.getId());
|
log.warn("{}][{}] Fetched null state from DB", device.getName(), device.getId());
|
||||||
}
|
}
|
||||||
@ -430,11 +432,20 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
|
|||||||
deviceStates.put(state.getDeviceId(), state);
|
deviceStates.put(state.getDeviceId(), state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState() {
|
void updateState() {
|
||||||
long ts = System.currentTimeMillis();
|
final long ts = System.currentTimeMillis();
|
||||||
Set<DeviceId> deviceIds = new HashSet<>(deviceStates.keySet());
|
|
||||||
log.debug("Calculating state updates for {} devices", deviceStates.size());
|
log.debug("Calculating state updates for {} devices", deviceStates.size());
|
||||||
|
Set<DeviceId> deviceIds = new HashSet<>(deviceStates.keySet());
|
||||||
for (DeviceId deviceId : deviceIds) {
|
for (DeviceId deviceId : deviceIds) {
|
||||||
|
updateState(ts, deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateState(DeviceId deviceId) {
|
||||||
|
updateState(System.currentTimeMillis(), deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateState(long ts, DeviceId deviceId) {
|
||||||
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
|
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
|
||||||
log.trace("Processing state {} for device {}", stateData, deviceId);
|
log.trace("Processing state {} for device {}", stateData, deviceId);
|
||||||
if (stateData != null) {
|
if (stateData != null) {
|
||||||
@ -452,7 +463,6 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
|
|||||||
deviceLastSavedActivity.remove(deviceId);
|
deviceLastSavedActivity.remove(deviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private DeviceStateData getOrFetchDeviceStateData(DeviceId deviceId) {
|
private DeviceStateData getOrFetchDeviceStateData(DeviceId deviceId) {
|
||||||
DeviceStateData deviceStateData = deviceStates.get(deviceId);
|
DeviceStateData deviceStateData = deviceStates.get(deviceId);
|
||||||
@ -492,39 +502,58 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) {
|
private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) {
|
||||||
|
ListenableFuture<DeviceStateData> future;
|
||||||
if (persistToTelemetry) {
|
if (persistToTelemetry) {
|
||||||
ListenableFuture<List<TsKvEntry>> tsData = tsService.findLatest(TenantId.SYS_TENANT_ID, device.getId(), PERSISTENT_ATTRIBUTES);
|
ListenableFuture<List<TsKvEntry>> tsData = tsService.findLatest(TenantId.SYS_TENANT_ID, device.getId(), PERSISTENT_ATTRIBUTES);
|
||||||
return Futures.transform(tsData, extractDeviceStateData(device), dbCallbackExecutorService);
|
future = Futures.transform(tsData, extractDeviceStateData(device), dbCallbackExecutorService);
|
||||||
} else {
|
} else {
|
||||||
ListenableFuture<List<AttributeKvEntry>> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES);
|
ListenableFuture<List<AttributeKvEntry>> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES);
|
||||||
return Futures.transform(attrData, extractDeviceStateData(device), dbCallbackExecutorService);
|
future = Futures.transform(attrData, extractDeviceStateData(device), dbCallbackExecutorService);
|
||||||
}
|
}
|
||||||
|
return transformInactivityTimeout(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
// voba - schwarz fix to use timeseries/attributes for inactivity timeout
|
||||||
|
private ListenableFuture<DeviceStateData> transformInactivityTimeout(ListenableFuture<DeviceStateData> future) {
|
||||||
|
return Futures.transformAsync(future, deviceStateData -> {
|
||||||
|
if (!persistToTelemetry || deviceStateData.getState().getInactivityTimeout() != TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec)) {
|
||||||
|
return future; //fail fast
|
||||||
|
}
|
||||||
|
final SettableFuture<DeviceStateData> resultFuture = SettableFuture.create();
|
||||||
|
Futures.addCallback(
|
||||||
|
attributesService.find(TenantId.SYS_TENANT_ID, deviceStateData.getDeviceId(), SERVER_SCOPE, INACTIVITY_TIMEOUT),
|
||||||
|
new FutureCallback<Optional<AttributeKvEntry>>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Optional<AttributeKvEntry> inactivityTimeoutOpt) {
|
||||||
|
inactivityTimeoutOpt.flatMap(KvEntry::getLongValue).ifPresent((inactivityTimeout) -> {
|
||||||
|
if (inactivityTimeout > 0) {
|
||||||
|
deviceStateData.getState().setInactivityTimeout(inactivityTimeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resultFuture.set(deviceStateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
resultFuture.setException(t);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dbCallbackExecutorService);
|
||||||
|
return resultFuture;
|
||||||
|
}, dbCallbackExecutorService);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T extends KvEntry> Function<List<T>, DeviceStateData> extractDeviceStateData(Device device) {
|
private <T extends KvEntry> Function<List<T>, DeviceStateData> extractDeviceStateData(Device device) {
|
||||||
return new Function<List<T>, DeviceStateData>() {
|
return new Function<List<T>, DeviceStateData>() {
|
||||||
@Nullable
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public DeviceStateData apply(@Nullable List<T> data) {
|
public DeviceStateData apply(@Nullable List<T> data) {
|
||||||
try {
|
try {
|
||||||
long lastActivityTime = getEntryValue(data, LAST_ACTIVITY_TIME, 0L);
|
long lastActivityTime = getEntryValue(data, LAST_ACTIVITY_TIME, 0L);
|
||||||
long inactivityAlarmTime = getEntryValue(data, INACTIVITY_ALARM_TIME, 0L);
|
long inactivityAlarmTime = getEntryValue(data, INACTIVITY_ALARM_TIME, 0L);
|
||||||
long inactivityTimeout = getEntryValue(data, INACTIVITY_TIMEOUT, TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec));
|
long inactivityTimeout = getEntryValue(data, INACTIVITY_TIMEOUT, TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec));
|
||||||
// voba - fix to use timeseries/attributes for inactivity timeout
|
//Actual active state by wall-clock will updated outside this method. This method is only for fetch persistent state
|
||||||
if (persistToTelemetry && inactivityTimeout == TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec)) {
|
final boolean active = getEntryValue(data, ACTIVITY_STATE, false);
|
||||||
try {
|
|
||||||
Optional<AttributeKvEntry> inactivityTimeoutOpt =
|
|
||||||
attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), SERVER_SCOPE, INACTIVITY_TIMEOUT).get();
|
|
||||||
if (inactivityTimeoutOpt.isPresent() && inactivityTimeoutOpt.get().getLongValue().isPresent()
|
|
||||||
&& inactivityTimeoutOpt.get().getLongValue().get() > 0) {
|
|
||||||
inactivityTimeout = inactivityTimeoutOpt.get().getLongValue().get();
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: voba - do we need to calculate it or it's better to fetch from DB directly?
|
|
||||||
// boolean active = System.currentTimeMillis() < lastActivityTime + inactivityTimeout;
|
|
||||||
boolean active = getEntryValue(data, ACTIVITY_STATE, false);
|
|
||||||
DeviceState deviceState = DeviceState.builder()
|
DeviceState deviceState = DeviceState.builder()
|
||||||
.active(active)
|
.active(active)
|
||||||
.lastConnectTime(getEntryValue(data, LAST_CONNECT_TIME, 0L))
|
.lastConnectTime(getEntryValue(data, LAST_CONNECT_TIME, 0L))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user