[netatmo] Ensure to close all scheduled jobs (#16056)

* Close all jobs

---------

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2023-12-16 10:10:35 +01:00 committed by GitHub
parent 5a70137e7b
commit 7a1884d4d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 92 additions and 39 deletions

View File

@ -293,7 +293,7 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
logger.info("Unable to instantiate {}, expected scope {} is not active", clazz, expected);
}
} catch (SecurityException | ReflectiveOperationException e) {
logger.warn("Error invoking RestManager constructor for class {} : {}", clazz, e.getMessage());
logger.warn("Error invoking RestManager constructor for class {}: {}", clazz, e.getMessage());
}
}
return (T) managers.get(clazz);
@ -319,7 +319,7 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
request.content(inputStreamContentProvider, contentType);
request.header(HttpHeader.ACCEPT, "application/json");
}
logger.trace(" -with payload : {} ", payload);
logger.trace(" -with payload: {} ", payload);
}
if (isLinked(requestCountChannelUID)) {
@ -331,13 +331,13 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
}
updateState(requestCountChannelUID, new DecimalType(requestsTimestamps.size()));
}
logger.trace(" -with headers : {} ",
logger.trace(" -with headers: {} ",
String.join(", ", request.getHeaders().stream().map(HttpField::toString).toList()));
ContentResponse response = request.send();
Code statusCode = HttpStatus.getCode(response.getStatus());
String responseBody = new String(response.getContent(), StandardCharsets.UTF_8);
logger.trace(" -returned : code {} body {}", statusCode, responseBody);
logger.trace(" -returned: code {} body {}", statusCode, responseBody);
if (statusCode == Code.OK) {
return deserializer.deserialize(clazz, responseBody);
@ -347,7 +347,7 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
try {
exception = new NetatmoException(deserializer.deserialize(ApiError.class, responseBody));
} catch (NetatmoException e) {
exception = new NetatmoException("Error deserializing error : %s".formatted(statusCode.getMessage()));
exception = new NetatmoException("Error deserializing error: %s".formatted(statusCode.getMessage()));
}
throw exception;
} catch (NetatmoException e) {
@ -359,10 +359,10 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
throw new NetatmoException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage()));
throw new NetatmoException("Request interrupted");
} catch (TimeoutException | ExecutionException e) {
if (retryCount > 0) {
logger.debug("Request timedout, retry counter : {}", retryCount);
logger.debug("Request timedout, retry counter: {}", retryCount);
return executeUri(uri, method, clazz, payload, contentType, retryCount - 1);
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/request-time-out");
@ -427,7 +427,7 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
});
}
} catch (NetatmoException e) {
logger.warn("Error while identifying all modules : {}", e.getMessage());
logger.warn("Error while identifying all modules: {}", e.getMessage());
}
}

View File

@ -17,7 +17,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
@ -30,6 +29,7 @@ import org.openhab.binding.netatmo.internal.config.NAThingConfiguration;
import org.openhab.binding.netatmo.internal.handler.capability.Capability;
import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
import org.openhab.binding.netatmo.internal.handler.capability.ParentUpdateCapability;
import org.openhab.binding.netatmo.internal.handler.capability.RefreshCapability;
import org.openhab.binding.netatmo.internal.handler.capability.RestCapability;
import org.openhab.core.config.core.Configuration;
@ -220,16 +220,15 @@ public interface CommonInterface {
setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, null);
} else if (!ThingStatus.ONLINE.equals(bridge.getStatus())) {
setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
removeRefreshCapability();
getCapabilities().remove(RefreshCapability.class);
getCapabilities().remove(ParentUpdateCapability.class);
} else {
setThingStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, null);
setRefreshCapability();
getScheduler().schedule(() -> {
CommonInterface bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
bridgeHandler.expireData();
}
}, 1, TimeUnit.SECONDS);
if (ModuleType.ACCOUNT.equals(getModuleType().getBridge())) {
NAThingConfiguration config = getThing().getConfiguration().as(NAThingConfiguration.class);
getCapabilities().put(new RefreshCapability(this, config.refreshInterval));
}
getCapabilities().put(new ParentUpdateCapability(this));
}
}
@ -237,20 +236,6 @@ public interface CommonInterface {
return ModuleType.from(getThing().getThingTypeUID());
}
default void setRefreshCapability() {
if (ModuleType.ACCOUNT.equals(getModuleType().getBridge())) {
NAThingConfiguration config = getThing().getConfiguration().as(NAThingConfiguration.class);
getCapabilities().put(new RefreshCapability(this, getScheduler(), config.refreshInterval));
}
}
default void removeRefreshCapability() {
Capability refreshCap = getCapabilities().remove(RefreshCapability.class);
if (refreshCap != null) {
refreshCap.dispose();
}
}
default void commonDispose() {
getCapabilities().values().forEach(Capability::dispose);
}

View File

@ -16,6 +16,7 @@ import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* {@link CapabilityMap} is a specialized Map designed to store capabilities
@ -40,4 +41,12 @@ public class CapabilityMap extends ConcurrentHashMap<Class<?>, Capability> {
T cap = (T) super.get(clazz);
return Optional.ofNullable(cap);
}
public <T extends Capability> void remove(Class<?> clazz) {
@Nullable
Capability cap = super.remove(clazz);
if (cap != null) {
cap.dispose();
}
}
}

View File

@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.netatmo.internal.handler.capability;
import java.util.Optional;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link ParentUpdateCapability} is the class used to request data update upon initialization of a module
*
* @author Gaël L'hopital - Initial contribution
*
*/
@NonNullByDefault
public class ParentUpdateCapability extends Capability {
private static final int DEFAULT_DELAY_S = 2;
private final Logger logger = LoggerFactory.getLogger(ParentUpdateCapability.class);
private Optional<ScheduledFuture<?>> job = Optional.empty();
public ParentUpdateCapability(CommonInterface handler) {
super(handler);
}
@Override
public void initialize() {
job = Optional.of(handler.getScheduler().schedule(() -> {
logger.debug("Requesting parents data update for Thing {}", handler.getId());
CommonInterface bridgeHandler = handler.getBridgeHandler();
if (bridgeHandler != null) {
bridgeHandler.expireData();
}
}, DEFAULT_DELAY_S, TimeUnit.SECONDS));
}
@Override
public void dispose() {
job.ifPresent(j -> j.cancel(true));
job = Optional.empty();
super.dispose();
}
}

View File

@ -18,7 +18,6 @@ import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@ -42,7 +41,6 @@ public class RefreshCapability extends Capability {
private static final Duration OFFLINE_INTERVAL = Duration.of(15, MINUTES);
private final Logger logger = LoggerFactory.getLogger(RefreshCapability.class);
private final ScheduledExecutorService scheduler;
private Duration dataValidity;
private Instant dataTimeStamp = Instant.now();
@ -50,10 +48,13 @@ public class RefreshCapability extends Capability {
private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
private boolean refreshConfigured;
public RefreshCapability(CommonInterface handler, ScheduledExecutorService scheduler, int refreshInterval) {
public RefreshCapability(CommonInterface handler, int refreshInterval) {
super(handler);
this.scheduler = scheduler;
this.dataValidity = Duration.ofSeconds(Math.max(0, refreshInterval));
}
@Override
public void initialize() {
this.refreshConfigured = !probing();
freeJobAndReschedule(2);
}
@ -109,7 +110,7 @@ public class RefreshCapability extends Capability {
refreshConfigured = true;
logger.debug("Data validity period identified to be {}", dataValidity);
} else {
logger.debug("Data validity period not yet found - data timestamp unchanged");
logger.debug("Data validity period not yet found, data timestamp unchanged");
}
}
dataTimeStamp = tsInstant;
@ -117,8 +118,8 @@ public class RefreshCapability extends Capability {
}
private void freeJobAndReschedule(long delay) {
refreshJob.ifPresent(job -> job.cancel(false));
refreshJob = Optional
.ofNullable(delay == 0 ? null : scheduler.schedule(() -> proceedWithUpdate(), delay, TimeUnit.SECONDS));
refreshJob.ifPresent(job -> job.cancel(true));
refreshJob = Optional.ofNullable(delay == 0 ? null
: handler.getScheduler().schedule(() -> proceedWithUpdate(), delay, TimeUnit.SECONDS));
}
}