Copilot commented on code in PR #17250:
URL: https://github.com/apache/iotdb/pull/17250#discussion_r2887989684
##########
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/externalservice/ExternalServiceManagementService.java:
##########
@@ -417,6 +417,25 @@ public static ExternalServiceManagementService
getInstance() {
return ExternalServiceManagementServiceHolder.INSTANCE;
}
+ public void clearServiceUserCache(String userName) {
+ serviceInfos
+ .values()
+ .forEach(
+ serviceInfo -> {
+ // clear user cache of service with RUNNING state
+ if (serviceInfo.getState() == RUNNING) {
+ IExternalService serviceInstance =
serviceInfo.getServiceInstance();
+ // serviceInstance maybe null when an exception occurs during
the start of certain
+ // service in restoreRunningServiceInstance method
+ if (serviceInstance != null) {
+ // only clear user cache of the instance successfully
started and running
+ runWithServiceClassLoader(
+ serviceInfo, () ->
serviceInstance.clearUserCache(userName));
Review Comment:
If any external service's `clearUserCache` throws, the exception will
propagate through `runWithServiceClassLoader` and can break user/role cache
invalidation in the DataNode. It would be safer to catch `Throwable` (or at
least `Exception`) per service here, log it, and continue invalidating other
services and the local author cache.
```suggestion
try {
runWithServiceClassLoader(
serviceInfo, () ->
serviceInstance.clearUserCache(userName));
} catch (Throwable t) {
LOGGER.error(
"Failed to clear user cache for external service {}
and user {}.",
serviceInfo.getServiceName(),
userName,
t);
}
```
##########
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/externalservice/ExternalServiceManagementService.java:
##########
@@ -417,6 +417,25 @@ public static ExternalServiceManagementService
getInstance() {
return ExternalServiceManagementServiceHolder.INSTANCE;
}
+ public void clearServiceUserCache(String userName) {
+ serviceInfos
+ .values()
+ .forEach(
+ serviceInfo -> {
+ // clear user cache of service with RUNNING state
+ if (serviceInfo.getState() == RUNNING) {
+ IExternalService serviceInstance =
serviceInfo.getServiceInstance();
+ // serviceInstance maybe null when an exception occurs during
the start of certain
+ // service in restoreRunningServiceInstance method
+ if (serviceInstance != null) {
+ // only clear user cache of the instance successfully
started and running
+ runWithServiceClassLoader(
+ serviceInfo, () ->
serviceInstance.clearUserCache(userName));
+ }
+ }
+ });
Review Comment:
`serviceInfos` is a `HashMap` annotated with `@GuardedBy("lock")`, but
`clearServiceUserCache` iterates it without acquiring the lock. This can race
with `createService/startService/stopService/dropService` and may throw
`ConcurrentModificationException` or observe inconsistent state. Consider
taking the read lock (or snapshotting `serviceInfos.values()` under the lock)
before iterating.
```suggestion
lock.readLock().lock();
try {
serviceInfos
.values()
.forEach(
serviceInfo -> {
// clear user cache of service with RUNNING state
if (serviceInfo.getState() == RUNNING) {
IExternalService serviceInstance =
serviceInfo.getServiceInstance();
// serviceInstance maybe null when an exception occurs
during the start of
// certain service in restoreRunningServiceInstance method
if (serviceInstance != null) {
// only clear user cache of the instance successfully
started and running
runWithServiceClassLoader(
serviceInfo, () ->
serviceInstance.clearUserCache(userName));
}
}
});
} finally {
lock.readLock().unlock();
}
```
##########
iotdb-api/external-service-api/src/main/java/org/apache/iotdb/externalservice/api/IExternalService.java:
##########
@@ -30,4 +30,7 @@ public interface IExternalService {
* guarantee to putBack thread or thread pool.
*/
void stop();
+
+ /** Clear user cache of current service. */
+ void clearUserCache(String userName);
Review Comment:
Adding an abstract method to `IExternalService` is a breaking API change for
any existing user-defined external service implementations (they will no longer
compile). If backward compatibility is desired, consider making this a
`default` no-op method in the interface so older implementations keep working
without modification.
```suggestion
default void clearUserCache(String userName) {}
```
##########
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java:
##########
@@ -130,6 +131,7 @@ public static IAuthorityFetcher getAuthorityFetcher() {
public static boolean invalidateCache(String username, String roleName) {
PipeInsertionDataNodeListener.getInstance().invalidateAllCache();
+
ExternalServiceManagementService.getInstance().clearServiceUserCache(username);
Review Comment:
`invalidateCache` now calls into the external service manager before
invalidating the DataNode author cache. If `clearServiceUserCache` fails (e.g.,
runtime exception from a user-defined service), this method will currently
propagate the error and skip invalidating the local cache. Consider wrapping
the external-service call in a try/catch and proceeding with local invalidation
even if external cache clearing fails.
```suggestion
try {
ExternalServiceManagementService.getInstance().clearServiceUserCache(username);
} catch (RuntimeException e) {
// Ignore failures from external service cache clearing to ensure
local cache is still invalidated.
}
```
##########
external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/filter/UserCache.java:
##########
@@ -51,6 +52,15 @@ public void setUser(String key, User user) {
cache.put(key, user);
}
+ public void clearUserCache(String userName) {
+ for (Map.Entry<String, User> mapValue : cache.asMap().entrySet()) {
+ if (mapValue.getValue().getUsername().equals(userName)) {
+ cache.invalidate(mapValue.getKey());
+ return;
+ }
+ }
+ }
Review Comment:
`clearUserCache` returns after invalidating the first cache entry for the
username, but the cache key is the full Authorization header (which can have
multiple distinct values per user, e.g., after password changes). To ensure
permission changes take effect consistently, invalidate *all* entries whose
cached `User` matches the username (e.g., collect matching keys and call
`invalidateAll`).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]