Skip to content

ServiceConfigurationError when calling async functions on Hazelcast IMap #30182

Open
@floifykevin

Description

Describe the bug

We currently have a Quarkus application that uses RestEasy Reactive and integrates with Hazelcast for caching. The caching works initially. However, after a period of time (5-10 minutes) when the code is accessed, a SmallRye exception is thrown. Once thrown, the code path will always fail with the same exception. I've read up on Context Propagation and review other tickets, but can't seem to figure out why the correct classloader is not being used. We would appreciate any insight into this that you can provide.

Expected behavior

I would expect the initial behavior of not throwing an exception to always be the case.

Actual behavior

An exception is thrown:

ERROR [or.jb.re.re.co.co.AbstractResteasyReactiveContext] (vert.x-eventloop-thread-1) Request failed: java.util.ServiceConfigurationError: io.smallrye.config.SmallRyeConfigFactory: io.quarkus.runtime.configuration.QuarkusConfigFactory not a subtype
	at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:593)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1244)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1273)
	at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1309)
	at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1393)
	at io.smallrye.config.SmallRyeConfigProviderResolver.getFactoryFor(SmallRyeConfigProviderResolver.java:100)
	at io.smallrye.config.SmallRyeConfigProviderResolver.getConfig(SmallRyeConfigProviderResolver.java:76)
	at io.smallrye.config.SmallRyeConfigProviderResolver.getConfig(SmallRyeConfigProviderResolver.java:64)
	at org.eclipse.microprofile.config.ConfigProvider.getConfig(ConfigProvider.java:85)
	at io.smallrye.context.impl.DefaultValues.<init>(DefaultValues.java:41)
	at io.smallrye.context.SmallRyeContextManager.<init>(SmallRyeContextManager.java:63)
	at io.smallrye.context.SmallRyeContextManager$Builder.build(SmallRyeContextManager.java:337)
	at io.smallrye.context.SmallRyeContextManagerProvider.getContextManager(SmallRyeContextManagerProvider.java:48)
	at io.smallrye.context.SmallRyeContextManagerProvider.getContextManager(SmallRyeContextManagerProvider.java:37)
	at io.smallrye.context.SmallRyeContextManagerProvider.getManager(SmallRyeContextManagerProvider.java:97)
	at io.smallrye.context.SmallRyeThreadContext.getCurrentThreadContextOrDefaultContexts(SmallRyeThreadContext.java:160)
	at io.smallrye.mutiny.context.DefaultContextPropagationInterceptor.getThreadContext(DefaultContextPropagationInterceptor.java:12)
	at io.smallrye.mutiny.context.BaseContextPropagationInterceptor.decorate(BaseContextPropagationInterceptor.java:24)
	at io.smallrye.mutiny.infrastructure.Infrastructure.decorate(Infrastructure.java:145)
	at io.smallrye.mutiny.groups.UniCreate.completionStage(UniCreate.java:142)
	at io.smallrye.mutiny.groups.UniCreate.completionStage(UniCreate.java:78)
	at com.floify.cache.Cache.put(Cache.java:69)
	at com.floify.cache.Cache.lambda$computeIfAbsent$0(Cache.java:95)
	at io.smallrye.context.impl.wrappers.SlowContextualConsumer.accept(SlowContextualConsumer.java:21)
	at io.smallrye.mutiny.groups.UniOnNotNull.lambda$invoke$0(UniOnNotNull.java:40)
	at io.smallrye.context.impl.wrappers.SlowContextualConsumer.accept(SlowContextualConsumer.java:21)
	at io.smallrye.mutiny.operators.uni.UniOnItemConsume$UniOnItemComsumeProcessor.invokeEventHandler(UniOnItemConsume.java:77)
	at io.smallrye.mutiny.operators.uni.UniOnItemConsume$UniOnItemComsumeProcessor.onItem(UniOnItemConsume.java:42)
	at io.smallrye.mutiny.operators.uni.UniOnItemTransformToUni$UniOnItemTransformToUniProcessor.onItem(UniOnItemTransformToUni.java:60)
	at io.smallrye.mutiny.operators.uni.builders.UniCreateFromKnownItem$KnownItemSubscription.forward(UniCreateFromKnownItem.java:38)
	at io.smallrye.mutiny.operators.uni.builders.UniCreateFromKnownItem$KnownItemSubscription.access$100(UniCreateFromKnownItem.java:26)
	at io.smallrye.mutiny.operators.uni.builders.UniCreateFromKnownItem.subscribe(UniCreateFromKnownItem.java:23)
	at io.smallrye.mutiny.operators.AbstractUni.subscribe(AbstractUni.java:36)
	at io.smallrye.mutiny.operators.uni.UniOnItemTransformToUni$UniOnItemTransformToUniProcessor.performInnerSubscription(UniOnItemTransformToUni.java:81)
	at io.smallrye.mutiny.operators.uni.UniOnItemTransformToUni$UniOnItemTransformToUniProcessor.onItem(UniOnItemTransformToUni.java:57)
	at io.smallrye.mutiny.operators.uni.builders.UniCreateFromCompletionStage$CompletionStageUniSubscription.forwardResult(UniCreateFromCompletionStage.java:63)
	at io.smallrye.context.impl.wrappers.SlowContextualBiConsumer.accept(SlowContextualBiConsumer.java:21)
	at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:863)
	at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:841)
	at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

How to Reproduce?

Here is some of the code:

Hazelcast Producer:

public class HazelcastClientService {

  @Produces
  @ApplicationScoped
  @IfBuildProperty(name = "clustering.aws.enabled", stringValue = "false", enableIfMissing = true)
  HazelcastInstance getDefaultHazelcastClient(final ClusteringConfig config) {
    return HazelcastClient.newHazelcastClient(config.toClientConfig());
  }

  @Produces
  @ApplicationScoped
  @IfBuildProperty(name = "clustering.aws.enabled", stringValue = "true")
  HazelcastInstance getAwsHazelcastClient(
      final ClusteringConfig config, final AwsCredentials credentials) {
    return HazelcastClient.newHazelcastClient(config.toClientConfig(credentials));
  }
}

Cache manager:

@ApplicationScoped
public class Caches {
  @Inject HazelcastInstance hazelcast;
  @Inject
  ThreadContext threadContext;
  @Inject
  ManagedExecutor managedExecutor;
  private final ConcurrentMap<String, Cache> backingCaches = new ConcurrentHashMap<>();

  public Cache<User> getUserByEmailCache() {
    return getCache("userByEmailCache", User.class);
  }

  public Cache<Team> getTeamByDomainCache() {
    return getCache("teamByDomainCache", Team.class);
  }

  public Cache<UserState> getUserStateByEmailCache() {
    return getCache("userStateByEmailCache", UserState.class);
  }

  @SuppressWarnings("unchecked")
  private <T> Cache<T> getCache(final String key, final Class<T> type) {
    Cache<T> cache = backingCaches.get(key);

    if (cache == null) {
      cache = new Cache<>(threadContext, managedExecutor, hazelcast.getMap(key), type);

      backingCaches.put(key, cache);
    }

    return cache;
  }
}

Cache:

@Slf4j
public class Cache<T> {
  protected final IMap<String, String> backingCache;
  @Getter protected final ObjectMapper mapper;
  @Getter protected final Class<T> type;
  private final ThreadContext threadContext;
  private final ManagedExecutor managedExecutor;

  public Cache(final IMap<String, String> cache, final Class<T> type) {
    this.mapper = new ObjectMapper();
    this.mapper.findAndRegisterModules();
    this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    this.backingCache = cache;
    this.type = type;
    this.threadContext = null;
    this.managedExecutor = null;
  }

  public Cache(ThreadContext threadContext,
      ManagedExecutor managedExecutor, IMap<String, String> cache, Class<T> type) {
    this.mapper = new ObjectMapper();
    this.mapper.findAndRegisterModules();
    this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    this.backingCache = cache;
    this.type = type;
    this.threadContext = threadContext;
    this.managedExecutor = managedExecutor;
  }

  public Uni<T> get(final String key) {
    if (StringUtils.isBlank(key)) {
      return Uni.createFrom().nullItem();
    }

    CompletionStage<T> get = threadContext
        .withContextCapture(backingCache.getAsync(key))
        .thenApplyAsync(this::deserialize, managedExecutor);

    return Uni.createFrom().completionStage(get);
  }

  public Uni<T> put(final String key, final T value) {
    if (StringUtils.isBlank(key) || value == null) {
      return Uni.createFrom().nullItem();
    }

    try {
      CompletionStage<T> put = threadContext
          .withContextCapture(backingCache.putAsync(key, mapper.writeValueAsString(value)))
          .thenApplyAsync(this::deserialize, managedExecutor);

      return Uni.createFrom().completionStage(put);
    } catch (JsonProcessingException exception) {
      log.error("Could not serialize object for key {}", key, exception);

      return Uni.createFrom().failure(exception);
    }
  }

  public Uni<T> delete(final String key) {
    if (StringUtils.isBlank(key)) {
      return Uni.createFrom().nullItem();
    }

    CompletionStage<T> delete = threadContext
        .withContextCapture(backingCache.removeAsync(key))
        .thenApplyAsync(this::deserialize, managedExecutor);

    return Uni.createFrom().completionStage(delete);
  }

  public Uni<T> computeIfAbsent(final String key, final Supplier<Uni<T>> computer) {
    // spotless:off
    return get(key)
        .onItem()
        .ifNull().switchTo(computer::get)
        .onItem()
        .ifNotNull().invoke(value -> put(key, value));
    // spotless:on
  }

  private T deserialize(final String json) {
    try {
      if (StringUtils.isBlank(json)) {
        return null;
      }

      return mapper.readValue(json, type);
    } catch (JsonProcessingException exception) {
      log.error("Could not deserialize cached json {}", json, exception);

      return null;
    }
  }
}

Output of uname -a or ver

Darwin MacBook-Pro.local 21.6.0 Darwin Kernel Version 21.6.0: Wed Aug 10 14:25:27 PDT 2022; root:xnu-8020.141.5~2/RELEASE_X86_64 x86_64

Output of java -version

openjdk version "17" 2021-09-14 OpenJDK Runtime Environment (build 17+35-2724) OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.14.2.Final

Build tool (ie. output of mvnw --version or gradlew --version)

Gradle 7.4.2

Additional information

No response

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions