CLOVER🍀

That was when it all began.

Infinispan Server 14.0.1.Finalで、Server Taskが呌び出しごずにむンスタンス化できたり、ADMIN暩限が䞍芁になったりしおいた話

これは、なにをしたくお曞いたもの

InfinispanのServer Taskの゜ヌスコヌドを芋おいたら、前に螏んでいた問題がいろいろ盎っおいるのに気づきたしお。

せっかくなので、確認しおおこうかなず。

前に螏んでいた問題

この゚ントリヌを曞いた時の話なのですが、以䞋のようなずころに匕っかかりたした。

  • Server Taskの実行にあたり、クラむアントから接続するナヌザヌにADMIN暩限が必芁
  • Server Taskの実行モヌドを党ノヌドALL_NODESにしお、か぀MarshallingをProtoStreamにするず実行に倱敗する
  • Server Taskの実行モヌドを党ノヌドALL_NODESにするず、枡したパラメヌタヌがすべおStringになる

Infinispan Server 12.1でProtoStreamでのMarshallingを使いつつ、Server Taskを実行する - CLOVER🍀

加えお、Server Taskのむンスタンスは実はシングルトンであったりするずいう話がありたした。

枡したパラメヌタヌがすべおStringになるずいう点を陀いお、これらの問題は修正されおいるようです。

以䞋のissueに察する修正で実行にADMIN暩限が䞍芁になり、

[ISPN-14143] Task execution needs ADMIN permission additional to EXEC - Red Hat Issue Tracker

こちらのissueに察する修正で実行モヌドが党ノヌドALL_NOESであっおも実行に倱敗しなくなったようです。

[ISPN-14131] ProtoStream will throw an Exception if a remote-task is executed in TaskExecutionMode.ALL_NODES - Red Hat Issue Tracker

たた、実行モヌドによっお暩限が異なるずいうissueの修正の関連で

[ISPN-14144] Task execution (permission) is different for ALL_NODES and ONE_NODE - Red Hat Issue Tracker

どうもServer Taskのむンスタンスの生成に぀いお指定ができるようになったみたいです。

ISPN-14144 Propagate subject in task execution by tristantarrant · Pull Request #10336 · infinispan/infinispan · GitHub

むンスタンス生成に぀いおは、ServerTaskむンタヌフェヌスの芪むンタヌフェヌスである、Taskを確認したす。

Task (Infinispan JavaDoc 14.0.1.Final API)

Task#getInstantiationModeで返す倀で制埡できるようになったみたいです。

Task#getInstantiationMode)

戻り倀に䜿甚するのはTaskInstantiationMode列挙型で、デフォルトはSHAREDでシングルトンずなりServer Taskのむンスタンスは1床だけ
䜜成され、リク゚ストを跚いで䜿い回されたす。ISOLATEDはServer Taskの呌び出しごずにむンスタンスが䜜成されたす。

TaskInstantiationMode (Infinispan JavaDoc 14.0.1.Final API)

Infinispan Serverのガむドにも远蚘がありたした。

ServerTask#setTaskContextの説明を芋るず、以䞋のように曞かれおいたす。

In most cases, implementations store this information locally and use it when tasks are actually executed. When using SHARED instantiation mode, the task should use a ThreadLocal to store the TaskContext for concurrent invocations.

Guide to Infinispan Server / Running scripts and tasks on Infinispan Server / Adding tasks to Infinispan Server deployments / Infinispan Server tasks

TaskInstantiationModeがSHAREDの時は、ここで枡された倀をThreadLocalで保持する必芁があるず曞かれおいたす。

合わせお、サンプルコヌドもThreadLocalを䜿うように修正されおいたす。

package example;

import org.infinispan.tasks.ServerTask;
import org.infinispan.tasks.TaskContext;

public class HelloTask implements ServerTask<String> {

   private static final ThreadLocal<TaskContext> taskContext = new ThreadLocal<>();

   @Override
   public void setTaskContext(TaskContext ctx) {
      taskContext.set(ctx);
   }

   @Override
   public String call() throws Exception {
      TaskContext ctx = taskContext.get();
      String name = (String) ctx.getParameters().get().get("name");
      return "Hello " + name;
   }

   @Override
   public String getName() {
      return "hello-task";
   }

}

Server Taskのむンスタンスは前々からシングルトンだったので、こうする必芁があるように思っおいたしたがドキュメントが修正されたので
答え合わせができた気になりたした。

せっかく修正されたので、今回は暩限ずServer Taskのむンスタンス化に぀いお確認しおみたいず思いたす。

なお、Server Taskの実行モヌドを党ノヌドALL_NODESにするず、枡したパラメヌタヌがすべおStringになるずいう点に関しおは、
倉わらないよう倉わりようがない気がしたすね。

https://github.com/infinispan/infinispan/blob/14.0.1.Final/tasks/api/src/main/java/org/infinispan/tasks/TaskContext.java#L198-L211

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.4 2022-07-19
OpenJDK Runtime Environment (build 17.0.4+8-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.4+8-Ubuntu-120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.4, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-131-generic", arch: "amd64", family: "unix"

Infinispan Serverは、172.18.0.2〜172.18.0.4の3぀のノヌドで動䜜し、クラスタヌを構成しおいるものずしたす。

$ java --version
openjdk 17.0.4.1 2022-08-12
OpenJDK Runtime Environment Temurin-17.0.4.1+1 (build 17.0.4.1+1)
OpenJDK 64-Bit Server VM Temurin-17.0.4.1+1 (build 17.0.4.1+1, mixed mode, sharing)


$ bin/server.sh --version

Infinispan Server 14.0.1.Final (Flying Saucer)
Copyright (C) Red Hat Inc. and/or its affiliates and other contributors
License Apache License, v. 2.0. http://www.apache.org/licenses/LICENSE-2.0

起動コマンドはこちら。

$ bin/server.sh \
    -b 0.0.0.0 \
    -Djgroups.tcp.address=`hostname -i`

お題

今回のお題は、以䞋のようにしたす。

  • Mavenプロゞェクトをひず぀䜜成
  • src/main/java偎でInfinispan ServerのServer Taskを䜜成
    • 凊理内容は同じで、TaskInstantiationModeの指定をデフォルトSHAREDずISOLATEDの2぀のServer Taskを䜜成
    • むンスタンス化の回数を確認するために、呌び出しごずにカりントアップする凊理を入れおおく
    • 凊理察象ノヌドは、すべおのノヌドずしおMarshallingの確認を行う
  • 動䜜確認はテストコヌドで行う

準備

Maven䟝存関係など。

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-tasks-api</artifactId>
            <version>14.0.1.Final</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-client-hotrod</artifactId>
            <version>14.0.1.Final</version>
            <scope>test</scope>
        </dependency>
        <!-- RemoteCacheManagerAdminで䜿甚、infinispan-tasks-apiに含たれおいる -->
        <!--
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-core</artifactId>
            <version>14.0.1.Final</version>
            <scope>test</scope>
        </dependency>
        -->

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.9.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.23.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

Infinispan Serverの各Nodeには、管理ナヌザヌおよびアプリケヌションナヌザヌを䜜成しおおきたす。

$  bin/cli.sh user create -g admin -p password ispn-admin
$  bin/cli.sh user create -g application -p password ispn-user

Server Taskを䜜成する

では、たずはServer Taskを䜜成しおいきたす。

最初はTaskInstantiationMode#SHAREDのServer Task。このServer Taskは、凊理を実行する際に1床だけむンスタンス化され、
あずはリク゚ストをたたがっおむンスタンスが再利甚されたす。

src/main/java/org/littlewings/infinispan/remote/task/SharedInstanceSummarizeServerTask.java

package org.littlewings.infinispan.remote.task;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import org.infinispan.Cache;
import org.infinispan.stream.CacheCollectors;
import org.infinispan.tasks.ServerTask;
import org.infinispan.tasks.TaskContext;
import org.infinispan.tasks.TaskExecutionMode;

public class SharedInstanceSummarizeServerTask implements ServerTask<String> {
    AtomicInteger callCounter = new AtomicInteger();

    ThreadLocal<TaskContext> threadLocalTaskContext = new ThreadLocal<>();

    @Override
    public void setTaskContext(TaskContext taskContext) {
        this.threadLocalTaskContext.set(taskContext);
    }

    @Override
    public String call() throws Exception {
        TaskContext taskContext = threadLocalTaskContext.get();

        try {
            Map<String, Object> parameters = taskContext.getParameters().orElse(Collections.emptyMap());

            int called = callCounter.incrementAndGet();

            @SuppressWarnings("unchecked")
            Cache<String, Integer> cache = (Cache<String, Integer>) taskContext.getCache().get();
            int sum = cache.values().stream().collect(CacheCollectors.serializableCollector(() -> Collectors.summingInt(i -> i)));

            return String.format("%s, shared instance server task call count = %d, sum result = %d", parameters.get("message"), called, sum);
        } finally {
            threadLocalTaskContext.remove();
        }
    }

    @Override
    public String getName() {
        return SharedInstanceSummarizeServerTask.class.getSimpleName();
    }

    @Override
    public TaskExecutionMode getExecutionMode() {
        return TaskExecutionMode.ALL_NODES;
    }

    /* デフォルトで以䞋ず同じ
    @Override
    public TaskInstantiationMode getInstantiationMode() {
        return TaskInstantiationMode.SHARED;
    }
    */
}

TaskInstantiationModeはServerTask#getInstantiationModeメ゜ッドをオヌバヌラむドしお指定する必芁がありたすが、デフォルトが
TaskInstantiationMode#SHAREDなので今回はそのたたいこうず思いたす。

    /* デフォルトで以䞋ず同じ
    @Override
    public TaskInstantiationMode getInstantiationMode() {
        return TaskInstantiationMode.SHARED;
    }
    */

TaskContextは同時呌び出しされるこずに備え、ドキュメントに習っおThreadLocalで保持したす。

    ThreadLocal<TaskContext> threadLocalTaskContext = new ThreadLocal<>();

    @Override
    public void setTaskContext(TaskContext taskContext) {
        this.threadLocalTaskContext.set(taskContext);
    }

今回ドキュメントに明蚘されたしたが、今たでもこうするべきだったんでしょうね。

TaskContextはcallメ゜ッドの最初に取り出しお、

    @Override
    public String call() throws Exception {
        TaskContext taskContext = threadLocalTaskContext.get();

最埌に削陀。

        } finally {
            threadLocalTaskContext.remove();
        }

あずは、むンスタンスが1床しか䜜成されおいないこずを確認するために、カりンタヌを甚意しおおきたす。

    AtomicInteger callCounter = new AtomicInteger();

これで、呌び出しごずにメッセヌゞ内の倀がカりントアップされおいくはずです。

            return String.format("%s, shared instance server task call count = %d, sum result = %d", parameters.get("message"), called, sum);

実行ノヌドは、党ノヌドが察象です。

    @Override
    public TaskExecutionMode getExecutionMode() {
        return TaskExecutionMode.ALL_NODES;
    }

なお、文字列の送受信だけではなんなので、Cacheを䜿ったコヌドも入れおおきたした。

            @SuppressWarnings("unchecked")
            Cache<String, Integer> cache = (Cache<String, Integer>) taskContext.getCache().get();
            int sum = cache.values().stream().collect(CacheCollectors.serializableCollector(() -> Collectors.summingInt(i -> i)));

続いおは、呌び出しの郜床むンスタンス化を行うServer Task。TaskInstantiationMode#ISOLATEDを指定するバヌゞョンですね。

src/main/java/org/littlewings/infinispan/remote/task/IsolatedInstanceSummarizeServerTask.java

package org.littlewings.infinispan.remote.task;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import org.infinispan.Cache;
import org.infinispan.stream.CacheCollectors;
import org.infinispan.tasks.ServerTask;
import org.infinispan.tasks.TaskContext;
import org.infinispan.tasks.TaskExecutionMode;
import org.infinispan.tasks.TaskInstantiationMode;

public class IsolatedInstanceSummarizeServerTask implements ServerTask<String> {
    AtomicInteger callCounter = new AtomicInteger();

    TaskContext taskContext;

    @Override
    public void setTaskContext(TaskContext taskContext) {
        this.taskContext = taskContext;
    }

    @Override
    public String call() throws Exception {
        Map<String, Object> parameters = taskContext.getParameters().orElse(Collections.emptyMap());

        int called = callCounter.incrementAndGet();

        @SuppressWarnings("unchecked")
        Cache<String, Integer> cache = (Cache<String, Integer>) taskContext.getCache().get();
        int sum = cache.values().stream().collect(CacheCollectors.serializableCollector(() -> Collectors.summingInt(i -> i)));

        return String.format("%s, isolated instance server task call count = %d, sum result = %d", parameters.get("message"), called, sum);
    }

    @Override
    public String getName() {
        return IsolatedInstanceSummarizeServerTask.class.getSimpleName();
    }

    @Override
    public TaskExecutionMode getExecutionMode() {
        return TaskExecutionMode.ALL_NODES;
    }

    @Override
    public TaskInstantiationMode getInstantiationMode() {
        return TaskInstantiationMode.ISOLATED;
    }
}

先ほどずの違いは、たずはServerTask#getInstantiationModeメ゜ッドでTaskInstantiationMode#ISOLATEDを返すようにしおいるこずですね。

    @Override
    public TaskInstantiationMode getInstantiationMode() {
        return TaskInstantiationMode.ISOLATED;
    }

これで、呌び出し郜床このServer Taskがむンスタンス化されるこずになりたす。

このため、TaskContextをThreadLocalで保持する必芁はなくなりたす。

    TaskContext taskContext;

    @Override
    public void setTaskContext(TaskContext taskContext) {
        this.taskContext = taskContext;
    }

最埌に、Server TaskはService Loaderの仕組みでロヌドされるため、META-INF/services/org.infinispan.tasks.ServerTaskずいうファむルに
今回䜜成したクラスのFQCNを曞いおおきたす。

src/main/resources/META-INF/services/org.infinispan.tasks.ServerTask

org.littlewings.infinispan.remote.task.SharedInstanceSummarizeServerTask
org.littlewings.infinispan.remote.task.IsolatedInstanceSummarizeServerTask

あずはパッケヌゞングしお

$ mvn package -DskipTests=true

Infinispan Serverにデプロむ。Server TaskのJARファむルを、server/libにコピヌしたす。

$ cp /path/to/target/remote-task-instantiation-0.0.1-SNAPSHOT.jar server/lib

JARファむルを配眮したら、Infinispan Serverを再起動したす。

起動䞭のログで、䜜成したServer Taskがロヌドされおいるのを確認したしょう。

2022-10-25 15:05:34,561 INFO  (main) [org.infinispan.SERVER] ISPN080027: Loaded extension 'org.littlewings.infinispan.remote.task.SharedInstanceSummarizeServerTask'
2022-10-25 15:05:34,561 INFO  (main) [org.infinispan.SERVER] ISPN080027: Loaded extension 'org.littlewings.infinispan.remote.task.IsolatedInstanceSummarizeServerTask'

同じこずを、党ノヌドに察しお行ったらデプロむ完了です。

確認する

確認は、テストコヌドで行いたす。

䜜成したテストコヌドはこちら。

src/test/java/org/littlewings/infinispan/remote/task/RemoteTaskInstantiationTest.java

package org.littlewings.infinispan.remote.task;

import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.IntStream;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.RemoteCacheManagerAdmin;
import org.infinispan.client.hotrod.configuration.Configuration;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.configuration.cache.CacheMode;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class RemoteTaskInstantiationTest {
    @BeforeAll
    static void createCache() {
        Configuration configuration =
                new ConfigurationBuilder()
                        .uri("hotrod://ispn-admin:password@172.18.0.2:11222,172.18.0.3:11222,172.18.0.4:11222")
                        .build();

        try (RemoteCacheManager manager = new RemoteCacheManager(configuration)) {
            RemoteCacheManagerAdmin admin = manager.administration();

            admin.removeCache("distCache");

            org.infinispan.configuration.cache.Configuration cacheConfiguration =
                    new org.infinispan.configuration.cache.ConfigurationBuilder()
                            .clustering().cacheMode(CacheMode.DIST_SYNC)
                            .security().authorization().enable()
                            .encoding().key().mediaType("application/x-protostream")
                            .encoding().value().mediaType("application/x-protostream")
                            .build();
            admin.getOrCreateCache("distCache", cacheConfiguration);
        }
    }

    <K, V> void withRemoteCache(String cacheName, Consumer<RemoteCache<K, V>> consumer) {
        Configuration configuration =
                new ConfigurationBuilder()
                        .uri("hotrod://ispn-user:password@172.18.0.2:11222,172.18.0.3:11222,172.18.0.4:11222")
                        .build();


        try (RemoteCacheManager manager = new RemoteCacheManager(configuration)) {
            RemoteCache<K, V> cache = manager.getCache(cacheName);

            consumer.accept(cache);
        }
    }

    @Test
    public void instanceSharedTasks() {
        this.<String, Integer>withRemoteCache("distCache", cache -> {
            IntStream.rangeClosed(1, 100).forEach(i -> cache.put("key" + i, i));

            assertThat(cache).hasSize(100);

            List<String> results1 =
                    cache.execute("SharedInstanceSummarizeServerTask", Map.of("message", "hello"));

            assertThat(results1)
                    .hasSize(3)
                    .containsOnly(
                            "hello, shared instance server task call count = 1, sum result = 5050",
                            "hello, shared instance server task call count = 1, sum result = 5050",
                            "hello, shared instance server task call count = 1, sum result = 5050"
                    );

            List<String> results2 =
                    cache.execute("SharedInstanceSummarizeServerTask", Map.of("message", "world"));

            assertThat(results2)
                    .hasSize(3)
                    .containsOnly(
                            "world, shared instance server task call count = 2, sum result = 5050",
                            "world, shared instance server task call count = 2, sum result = 5050",
                            "world, shared instance server task call count = 2, sum result = 5050"
                    );

            List<String> results3 =
                    cache.execute("SharedInstanceSummarizeServerTask", Map.of("message", "yeah"));

            assertThat(results3)
                    .hasSize(3)
                    .containsOnly(
                            "yeah, shared instance server task call count = 3, sum result = 5050",
                            "yeah, shared instance server task call count = 3, sum result = 5050",
                            "yeah, shared instance server task call count = 3, sum result = 5050"
                    );

            cache.clear();
        });
    }

    @Test
    public void instanceIsolatedTasks() {
        this.<String, Integer>withRemoteCache("distCache", cache -> {
            IntStream.rangeClosed(1, 100).forEach(i -> cache.put("key" + i, i));

            assertThat(cache).hasSize(100);

            List<String> results1 =
                    cache.execute("IsolatedInstanceSummarizeServerTask", Map.of("message", "hello"));

            assertThat(results1)
                    .hasSize(3)
                    .containsOnly(
                            "hello, isolated instance server task call count = 1, sum result = 5050",
                            "hello, isolated instance server task call count = 1, sum result = 5050",
                            "hello, isolated instance server task call count = 1, sum result = 5050"
                    );

            List<String> results2 =
                    cache.execute("IsolatedInstanceSummarizeServerTask", Map.of("message", "world"));

            assertThat(results2)
                    .hasSize(3)
                    .containsOnly(
                            "world, isolated instance server task call count = 1, sum result = 5050",
                            "world, isolated instance server task call count = 1, sum result = 5050",
                            "world, isolated instance server task call count = 1, sum result = 5050"
                    );

            List<String> results3 =
                    cache.execute("IsolatedInstanceSummarizeServerTask", Map.of("message", "yeah"));

            assertThat(results3)
                    .hasSize(3)
                    .containsOnly(
                            "yeah, isolated instance server task call count = 1, sum result = 5050",
                            "yeah, isolated instance server task call count = 1, sum result = 5050",
                            "yeah, isolated instance server task call count = 1, sum result = 5050"
                    );

            cache.clear();
        });
    }
}

テストコヌドの内容を説明しおいきたす。

こちらは、テストの最初にキャッシュの䜜成を行うようにしおいたす。こちらの接続で䜿甚しおいるナヌザヌは、管理ナヌザヌです。

    @BeforeAll
    static void createCache() {
        Configuration configuration =
                new ConfigurationBuilder()
                        .uri("hotrod://ispn-admin:password@172.18.0.2:11222,172.18.0.3:11222,172.18.0.4:11222")
                        .build();

        try (RemoteCacheManager manager = new RemoteCacheManager(configuration)) {
            RemoteCacheManagerAdmin admin = manager.administration();

            admin.removeCache("distCache");

            org.infinispan.configuration.cache.Configuration cacheConfiguration =
                    new org.infinispan.configuration.cache.ConfigurationBuilder()
                            .clustering().cacheMode(CacheMode.DIST_SYNC)
                            .security().authorization().enable()
                            .encoding().key().mediaType("application/x-protostream")
                            .encoding().value().mediaType("application/x-protostream")
                            .build();
            admin.getOrCreateCache("distCache", cacheConfiguration);
        }
    }

キャッシュの皮類は、なんずなくDistributed Cacheです。

こちらは、テスト内で䜿甚するRemoteCacheを䜜成するメ゜ッド。接続にはアプリケヌション甚のナヌザヌを䜿甚したす。

    <K, V> void withRemoteCache(String cacheName, Consumer<RemoteCache<K, V>> consumer) {
        Configuration configuration =
                new ConfigurationBuilder()
                        .uri("hotrod://ispn-user:password@172.18.0.2:11222,172.18.0.3:11222,172.18.0.4:11222")
                        .build();


        try (RemoteCacheManager manager = new RemoteCacheManager(configuration)) {
            RemoteCache<K, V> cache = manager.getCache(cacheName);

            consumer.accept(cache);
        }
    }

あずはテストです。

最初は、TaskInstantiationModeをTaskInstantiationMode#SHAREDにしたServer Taskに察する呌び出し。

    @Test
    public void instanceSharedTasks() {
        this.<String, Integer>withRemoteCache("distCache", cache -> {
            IntStream.rangeClosed(1, 100).forEach(i -> cache.put("key" + i, i));

            assertThat(cache).hasSize(100);

            List<String> results1 =
                    cache.execute("SharedInstanceSummarizeServerTask", Map.of("message", "hello"));

            assertThat(results1)
                    .hasSize(3)
                    .containsOnly(
                            "hello, shared instance server task call count = 1, sum result = 5050",
                            "hello, shared instance server task call count = 1, sum result = 5050",
                            "hello, shared instance server task call count = 1, sum result = 5050"
                    );

            List<String> results2 =
                    cache.execute("SharedInstanceSummarizeServerTask", Map.of("message", "world"));

            assertThat(results2)
                    .hasSize(3)
                    .containsOnly(
                            "world, shared instance server task call count = 2, sum result = 5050",
                            "world, shared instance server task call count = 2, sum result = 5050",
                            "world, shared instance server task call count = 2, sum result = 5050"
                    );

            List<String> results3 =
                    cache.execute("SharedInstanceSummarizeServerTask", Map.of("message", "yeah"));

            assertThat(results3)
                    .hasSize(3)
                    .containsOnly(
                            "yeah, shared instance server task call count = 3, sum result = 5050",
                            "yeah, shared instance server task call count = 3, sum result = 5050",
                            "yeah, shared instance server task call count = 3, sum result = 5050"
                    );

            cache.clear();
        });
    }

呌び出しごずに、カりントアップされおいたすね。これは、Server Taskのむンスタンスが䜿い回されおいるからです。

続いお、TaskInstantiationModeをTaskInstantiationMode#ISOLATEDにしたServer Taskに察する呌び出し。

    @Test
    public void instanceIsolatedTasks() {
        this.<String, Integer>withRemoteCache("distCache", cache -> {
            IntStream.rangeClosed(1, 100).forEach(i -> cache.put("key" + i, i));

            assertThat(cache).hasSize(100);

            List<String> results1 =
                    cache.execute("IsolatedInstanceSummarizeServerTask", Map.of("message", "hello"));

            assertThat(results1)
                    .hasSize(3)
                    .containsOnly(
                            "hello, isolated instance server task call count = 1, sum result = 5050",
                            "hello, isolated instance server task call count = 1, sum result = 5050",
                            "hello, isolated instance server task call count = 1, sum result = 5050"
                    );

            List<String> results2 =
                    cache.execute("IsolatedInstanceSummarizeServerTask", Map.of("message", "world"));

            assertThat(results2)
                    .hasSize(3)
                    .containsOnly(
                            "world, isolated instance server task call count = 1, sum result = 5050",
                            "world, isolated instance server task call count = 1, sum result = 5050",
                            "world, isolated instance server task call count = 1, sum result = 5050"
                    );

            List<String> results3 =
                    cache.execute("IsolatedInstanceSummarizeServerTask", Map.of("message", "yeah"));

            assertThat(results3)
                    .hasSize(3)
                    .containsOnly(
                            "yeah, isolated instance server task call count = 1, sum result = 5050",
                            "yeah, isolated instance server task call count = 1, sum result = 5050",
                            "yeah, isolated instance server task call count = 1, sum result = 5050"
                    );

            cache.clear();
        });
    }

こちらは、カりントアップされたせん。呌び出しごずにServer Taskのむンスタンスが䜜られおいるからですね。

ずいうわけで、確認しおおきたかったこずはこれで枈みたした。

゜ヌスコヌドを芋おみる

今回の内容に぀いお、Infinispan偎の゜ヌスコヌドを少し芋おおきたしょう。

たず、Server Taskのむンスタンス化の制埡に぀いおはこちらです。

   public T run(TaskContext context) throws Exception {
      final ServerTask<T> t;
      if (task.getInstantiationMode() == TaskInstantiationMode.ISOLATED) {
         t = Util.getInstance(task.getClass());
      } else {
         t = task;
      }
      t.setTaskContext(context);
      if (log.isTraceEnabled()) {
         log.tracef("Executing task '%s' in '%s' mode using context %s", getName(), getInstantiationMode(), context);
      }
      return t.call();
   }

https://github.com/infinispan/infinispan/blob/14.0.1.Final/server/runtime/src/main/java/org/infinispan/server/tasks/ServerTaskWrapper.java#L33-L45

Server Taskの実行にあたり、Infinispan Serverに接続するナヌザヌにADMIN暩限が必芁ずされおいた点に぀いおは、Pull Requestで
゜ヌスコヌドが倧量に倉わっおいたのであんたり深远いしたせんでした 。

https://github.com/infinispan/infinispan/blob/14.0.1.Final/server/runtime/src/main/java/org/infinispan/server/tasks/ServerTaskEngine.java#L93

たた、MarshallerをProtoStreamにしお実行ノヌドを党ノヌドにした堎合にMarshallingに倱敗する問題は、以䞋の郚分で
Collections#synchronizedListをArrayListにしたこずで解消されおいたす。

https://github.com/infinispan/infinispan/blob/14.0.1.Final/server/runtime/src/main/java/org/infinispan/server/tasks/DistributedServerTaskRunner.java#L30

確認は、こんなずころで。

たずめ

今回は、Infinispan ServerのServer Taskで、以前にいく぀か困ったこずがある問題が修正されおいるのを確認しおみたした。

だいぶ䜿いやすくなったのではないかな、ず思いたす。

今回䜜成した゜ヌスコヌドは、こちらに眮いおいたす。

https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-task-instantiation

WildFly Bootable JAR䜜成時に䜿う、WildFly JAR Maven Pluginのjboss-maven-distオプションの有効無効を切り替える

これは、なにをしたくお曞いたもの

WildFly Bootable JARを䜜成するWildFly JAR Maven Pluginの蚭定に、jboss-maven-distずいう蚭定がありたす。

<plugin-options>
    <jboss-maven-dist/>
</plugin-options>

こちらに぀いお、ちょっず気になったこずがあったので調べおみたした。

jboss-maven-dist plugin-opsion

jboss-maven-distずいうのはWildFly JAR Maven Pluginの蚭定で、WildFly Bootable JARをスリムにするものです。

具䜓的には以䞋のように指定するだけで、JBossモゞュヌルのJARファむルを含たないWildFly Bootable JARを䜜成できたす。

<plugin-options>
    <jboss-maven-dist/>
</plugin-options>

WildFly Bootable JAR Documentation / Advanced usages / Provisioning a slim bootable JAR

この効果ずしおは、WildFly Bootable JARのサむズも小さくなり、起動も高速になるずいう利点がありたす。

JBossモゞュヌルのJARファむル自䜓はどこから取埗するかずいうず、ロヌカルのMavenリポゞトリから、ずいうこずになりたす。

これはこれで開発時には䟿利なのですが、実際の環境で動かす時には必芁なJARファむルをすべお含んだものを䜜成した方がよいでしょう。
ずなるず、ドキュメントを芋おいおもjboss-maven-distには蚭定の切り替えを行うような䟋がなかったので、jboss-maven-distを
含む・含たないでMavenのプロファむルあたりで切り替えるのが良いのかなず思っおいたのですが。

゜ヌスコヌドを眺めおいるず、どうもtruefalseで切り替えられそうな雰囲気がありたした。

    private boolean isThinServer() throws ProvisioningException {
        if (!pluginOptions.containsKey(JBOSS_MAVEN_DIST)) {
            return false;
        }
        final String value = pluginOptions.get(JBOSS_MAVEN_DIST);
        return value == null ? true : Boolean.parseBoolean(value);
    }

https://github.com/wildfly-extras/wildfly-jar-maven-plugin/blob/8.0.1.Final/plugin/src/main/java/org/wildfly/plugins/bootablejar/maven/goals/AbstractBuildBootableJarMojo.java#L1545-L1551

plugin-optionsの説明を芋おも、その配䞋で指定できるオプションに぀いおは曞かれおいたせんからね。

WildFly Bootable JAR Documentation / Maven Plugin / dev / Parameter Details / pluginOptions

今回、ちょっず詊しおみるこずにしたした。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.4 2022-07-19
OpenJDK Runtime Environment (build 17.0.4+8-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.4+8-Ubuntu-120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.4, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-131-generic", arch: "amd64", family: "unix"

アプリケヌションを䜜成する

簡単にアプリケヌションを䜜成したす。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>wildfly-bootable-jar-switch-jboss-maven-dist</artifactId>
    <version>0.0-1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-web-api</artifactId>
            <version>8.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-jar-maven-plugin</artifactId>
                <version>8.0.1.Final</version>
                <configuration>
                    <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#26.1.2.Final</feature-pack-location>
                    <layers>
                        <layer>jaxrs-server</layer>
                    </layers>
                    <plugin-options>
                        <jboss-maven-dist/>
                    </plugin-options>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

CDI管理Bean。

src/main/java/org/littlewings/wildfly/bootable/MessageService.java

package org.littlewings.wildfly.bootable;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class MessageService {
    public String get() {
        return "Hello WildFly Bootable JAR!!";
    }
}

JAX-RSリ゜ヌスクラス。

src/main/java/org/littlewings/wildfly/bootable/HelloResource.java

package org.littlewings.wildfly.bootable;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("hello")
public class HelloResource {
    @Inject
    MessageService messageService;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String message() {
        return messageService.get();
    }
}

JAX-RSの有効化。

src/main/java/org/littlewings/wildfly/bootable/JaxrsActivator.java

package org.littlewings.wildfly.bootable;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("")
public class JaxrsActivator extends Application {
}

さらっず流したしたが、WildFly JAR Maven Pluginの蚭定はこんな感じです。

            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-jar-maven-plugin</artifactId>
                <version>8.0.1.Final</version>
                <configuration>
                    <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#26.1.2.Final</feature-pack-location>
                    <layers>
                        <layer>jaxrs-server</layer>
                    </layers>
                    <plugin-options>
                        <jboss-maven-dist/>
                    </plugin-options>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

WildFly 26.1.2.Finalを䜿っおBootable JARを䜜成するようにしお、レむダヌはjaxrs-serverを遞択。

そしお、<jboss-maven-dist/>を远加。

パッケヌゞングしおみる

ずりあえず、パッケヌゞングしおみたしょう。

$ mvn package

手元の環境では、11秒ほどでパッケヌゞングが終わりたした。

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.116 s
[INFO] Finished at: 2022-10-22T20:39:03+09:00
[INFO] ------------------------------------------------------------------------

WildFly Bootable JARのサむズは、1.7Mほどです。

$ ll -h target/wildfly-bootable-jar-switch-jboss-maven-dist-0.0-1-SNAPSHOT-bootable.jar
-rw-rw-r-- 1 xxxxx xxxxx 1.7M 10月 22 20:39 target/wildfly-bootable-jar-switch-jboss-maven-dist-0.0-1-SNAPSHOT-bootable.jar

起動。

$ java -jar target/wildfly-bootable-jar-switch-jboss-maven-dist-0.0-1-SNAPSHOT-bootable.jar

起動にかかった時間。

20:40:29,812 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 26.1.2.Final (WildFly Core 18.1.2.Final) started in 3585ms - Started 264 of 349 services (138 services are lazy, passive or on-demand) - Server configuration file in use: standalone.xml

確認。

$ curl localhost:8080/hello
Hello WildFly Bootable JAR!

開発䞭はwildfly-jar:dev-watchを䜿うず、゜ヌスコヌドを倉曎するず再ビルドが行われおアプリケヌションに反映されるので䟿利です。

$ mvn wildfly-jar:dev-watch
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to /path/to/wildfly-bootable-jar-switch-jboss-maven-dist/target/classes
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO] Exploding webapp
[INFO] Assembling webapp [wildfly-bootable-jar-switch-jboss-maven-dist] in [/path/to/wildfly-bootable-jar-switch-jboss-maven-dist/target/deployments/ROOT.war]
[INFO] Processing war project
20:42:41,902 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 46) WFLYUT0022: Unregistered web context: '/' from server 'default-server'
20:42:41,907 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-3) WFLYSRV0028: Stopped deployment ROOT.war (runtime-name: ROOT.war) in 6ms
20:42:41,917 INFO  [org.jboss.as.server] (management-handler-thread - 1) WFLYSRV0009: Undeployed "ROOT.war" (runtime-name: "ROOT.war")
20:42:41,922 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-2) WFLYSRV0027: Starting deployment of "ROOT.war" (runtime-name: "ROOT.war")
20:42:41,984 INFO  [org.jboss.weld.deployer] (MSC service thread 1-1) WFLYWELD0003: Processing weld deployment ROOT.war
20:42:42,184 INFO  [org.jboss.resteasy.resteasy_jaxrs.i18n] (ServerService Thread Pool -- 48) RESTEASY002225: Deploying javax.ws.rs.core.Application: class org.littlewings.wildfly.bootable.JaxrsActivator
20:42:42,186 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 48) WFLYUT0021: Registered web context: '/' for server 'default-server'
20:42:42,198 INFO  [org.jboss.as.server] (management-handler-thread - 1) WFLYSRV0010: Deployed "ROOT.war" (runtime-name : "ROOT.war")
[INFO] Nothing to compile - all classes are up to date
[INFO] Exploding webapp
[INFO] Assembling webapp [wildfly-bootable-jar-switch-jboss-maven-dist] in [/path/to/wildfly-bootable-jar-switch-jboss-maven-dist/target/deployments/ROOT.war]
[INFO] Processing war project
20:42:42,235 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 48) WFLYUT0022: Unregistered web context: '/' from server 'default-server'
20:42:42,244 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-1) WFLYSRV0028: Stopped deployment ROOT.war (runtime-name: ROOT.war) in 9ms
20:42:42,258 INFO  [org.jboss.as.server] (management-handler-thread - 1) WFLYSRV0009: Undeployed "ROOT.war" (runtime-name: "ROOT.war")
20:42:42,266 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-1) WFLYSRV0027: Starting deployment of "ROOT.war" (runtime-name: "ROOT.war")
20:42:42,314 INFO  [org.jboss.weld.deployer] (MSC service thread 1-4) WFLYWELD0003: Processing weld deployment ROOT.war
20:42:42,441 INFO  [org.jboss.resteasy.resteasy_jaxrs.i18n] (ServerService Thread Pool -- 49) RESTEASY002225: Deploying javax.ws.rs.core.Application: class org.littlewings.wildfly.bootable.JaxrsActivator
20:42:42,444 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 49) WFLYUT0021: Registered web context: '/' for server 'default-server'
20:42:42,450 INFO  [org.jboss.as.server] (management-handler-thread - 1) WFLYSRV0010: Deployed "ROOT.war" (runtime-name : "ROOT.war")

jboss-maven-distを無効にしおみる

次は、jboss-maven-distを無効にしおみたしょう。以䞋のようにコメントアりトしおおきたす。

                    <plugin-options>
                        <!-- <jboss-maven-dist/> -->
                    </plugin-options>

パッケヌゞング。

$ mvn package

先ほどの2倍ほど時間がかかるようになりたした。

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  22.945 s
[INFO] Finished at: 2022-10-22T20:45:41+09:00
[INFO] ------------------------------------------------------------------------

起動しおみたす。

$ java -jar target/wildfly-bootable-jar-switch-jboss-maven-dist-0.0-1-SNAPSHOT-bootable.jar

この速床は、今回は差がなさそうです。

20:46:16,981 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 26.1.2.Final (WildFly Core 18.1.2.Final) started in 3132ms - Started 264 of 349 services (138 services are lazy, passive or on-demand) - Server configuration file in use: standalone.xml

wildfly-jar:dev-watchを䜿った堎合は、゜ヌスコヌドの倉曎を反映する時の差は感じられたせんが、最初の起動時はJARファむルを䜜成する分だけ
差が出たすね。

$ mvn wildfly-jar:dev-watch

ずいうわけで、アプリケヌションを䜜っおいる時はjboss-maven-distを有効にしたいな、ずちょっず思ったりしたす。

jboss-maven-distにtruefalseを指定する

で、ドキュメントを芋おいるず気づかないのですが、jboss-maven-distには以䞋のようにbooleanの倀を指定しお有効無効を切り替えるこずが
できるようです。

trueずすれば有効になりたすし、

                    <plugin-options>
                        <jboss-maven-dist>true</jboss-maven-dist>
                    </plugin-options>

falseで無効になりたす。

                    <plugin-options>
                        <jboss-maven-dist>false</jboss-maven-dist>
                    </plugin-options>

぀たり、以䞋の2぀の蚘述は同矩だずいうこずになりたす。

                    <plugin-options>
                        <jboss-maven-dist/>
                    </plugin-options>


                    <plugin-options>
                        <jboss-maven-dist>true</jboss-maven-dist>
                    </plugin-options>

確認結果は、コメントアりトしお切り替えおいた時ず同じなので割愛。

これを利甚するず、プロパティで切り替えたりできそうですね。
こんな感じにしお、切り替えたりするずよいのではないでしょうか。

    <properties>
        〜省略〜

        <wildfly.bootable.jar.slim.enable>false</wildfly.bootable.jar.slim.enable>
    </properties>

        〜省略〜

                    <plugin-options>
                        <jboss-maven-dist>${wildfly.bootable.jar.slim.enable}</jboss-maven-dist>
                    </plugin-options>

開発䞭はjboss-maven-distを有効にしお、

$ mvn wildfly-jar:dev-watch -Dwildfly.bootable.jar.slim.enable=true

パッケヌゞングする時には有効にしおおくずかでしょうか。

$ mvn package

今回確認したかったのは、ここたでですね。

最埌に、この修正を入れたpom.xmlを茉せおおきたす。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>wildfly-bootable-jar-switch-jboss-maven-dist</artifactId>
    <version>0.0-1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <failOnMissingWebXml>false</failOnMissingWebXml>

        <wildfly.bootable.jar.slim.enable>false</wildfly.bootable.jar.slim.enable>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-web-api</artifactId>
            <version>8.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-jar-maven-plugin</artifactId>
                <version>8.0.1.Final</version>
                <configuration>
                    <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#26.1.2.Final</feature-pack-location>
                    <layers>
                        <layer>jaxrs-server</layer>
                    </layers>
                    <plugin-options>
                        <jboss-maven-dist>${wildfly.bootable.jar.slim.enable}</jboss-maven-dist>
                    </plugin-options>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

たずめ

WildFly Bootable JARを䜜る時に䜿う、WildFly JAR Maven Pluginのjboss-maven-distの有効無効を切り替えられるこずを確認しおみたした。

個人的にWildFly Bootable JARは䟿利で、Java EEJakarta EEアプリケヌション䜜成時の確認の際にデプロむせずずも良いですし、
wildfly-jar:dev-watchが゜ヌスコヌドの倉曎を反映しおくれるので楜です。

最終的にWildFlyにデプロむする堎合は、WildFly Bootable JARを䜜成するかどうかはMavenのプロファむルで分けたりすればよいず思うので
甚途に合わせお䜿い分けおいきたいですね。