tkalkirill commented on code in PR #4239: URL: https://github.com/apache/ignite-3/pull/4239#discussion_r1721765640
########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/AsyncApiOperation.java: ########## @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.FULL_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.KEY_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.TEST_TABLE_NAME; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.VALUE_TUPLE; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import org.apache.ignite.internal.streamer.SimplePublisher; +import org.apache.ignite.internal.table.partition.HashPartition; +import org.apache.ignite.table.Tuple; +import org.apache.ignite.table.mapper.Mapper; + +/** + * Asynchronous API operation. + */ +enum AsyncApiOperation { + TABLES_TABLES(refs -> refs.tables.tablesAsync()), + TABLES_TABLE(refs -> refs.tables.tableAsync(TEST_TABLE_NAME)), + + KVVIEW_GET(refs -> refs.kvView.getAsync(null, KEY_TUPLE)), + KVVIEW_GET_OR_DEFAULT(refs -> refs.kvView.getOrDefaultAsync(null, KEY_TUPLE, null)), + KVVIEW_GET_ALL(refs -> refs.kvView.getAllAsync(null, List.of(KEY_TUPLE))), + KVVIEW_CONTAINS(refs -> refs.kvView.containsAsync(null, KEY_TUPLE)), + KVVIEW_CONTAINS_ALL(refs -> refs.kvView.containsAllAsync(null, List.of(KEY_TUPLE))), + KVVIEW_PUT(refs -> refs.kvView.putAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_PUT_ALL(refs -> refs.kvView.putAllAsync(null, Map.of(KEY_TUPLE, VALUE_TUPLE))), + KVVIEW_GET_AND_PUT(refs -> refs.kvView.getAndPutAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_PUT_IF_ABSENT(refs -> refs.kvView.putIfAbsentAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_REMOVE(refs -> refs.kvView.removeAsync(null, KEY_TUPLE)), + KVVIEW_REMOVE_EXACT(refs -> refs.kvView.removeAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_REMOVE_ALL(refs -> refs.kvView.removeAllAsync(null, List.of(KEY_TUPLE))), + KVVIEW_GET_AND_REMOVE(refs -> refs.kvView.getAndRemoveAsync(null, KEY_TUPLE)), + KVVIEW_REPLACE(refs -> refs.kvView.replaceAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_REPLACE_EXACT(refs -> refs.kvView.replaceAsync(null, KEY_TUPLE, VALUE_TUPLE, VALUE_TUPLE)), + KVVIEW_GET_AND_REPLACE(refs -> refs.kvView.getAndReplaceAsync(null, KEY_TUPLE, VALUE_TUPLE)), + VKVIEW_STREAM_DATA(refs -> { + CompletableFuture<?> future; + try (var publisher = new SimplePublisher<Entry<Tuple, Tuple>>()) { + future = refs.kvView.streamData(publisher, null); + publisher.submit(Map.entry(KEY_TUPLE, VALUE_TUPLE)); + } + return future; + }), + KVVIEW_QUERY(refs -> refs.kvView.queryAsync(null, null)), + KVVIEW_QUERY_WITH_INDEX(refs -> refs.kvView.queryAsync(null, null, null)), + KVVIEW_QUERY_WITH_OPTIONS(refs -> refs.kvView.queryAsync(null, null, null, null)), + + TYPED_KVVIEW_GET_NULLABLE(refs -> refs.typedKvView.getNullableAsync(null, 1)), + TYPED_KVVIEW_GET_NULLABLE_AND_PUT(refs -> refs.typedKvView.getNullableAndPutAsync(null, 1, "one")), + TYPED_KVVIEW_GET_NULLABLE_AND_REMOVE(refs -> refs.typedKvView.getNullableAndRemoveAsync(null, 1)), + TYPED_KVVIEW_GET_NULLABLE_AND_REPLACE(refs -> refs.typedKvView.getNullableAndReplaceAsync(null, 1, "one")), + + MAPPED_KVVIEW_GET(refs -> refs.mappedKvView.getAsync(null, 1)), + + RECORDVIEW_GET(refs -> refs.recordView.getAsync(null, KEY_TUPLE)), Review Comment: Let's make `RECORDVIEW` -> `RECORD_VIEW` for all constants. ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/Record.java: ########## @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import org.apache.ignite.table.Tuple; + +/** + * Simple record for tests. + */ +@SuppressWarnings({"FieldCanBeLocal", "unused"}) +class Record { + private int id; + private String val; Review Comment: Maybe add `final`? ########## modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteServerImpl.java: ########## @@ -82,10 +86,33 @@ public class IgniteServerImpl implements IgniteServer { private final ClassLoader classLoader; - private volatile @Nullable IgniteImpl instance; + private final Executor asyncContinuationExecutor; + + /** Current Ignite instance. This field is not volatile to make hot path accesses from IgniteReference and other references + * faster (they always happen under a read lock, which guarantees visibility of changes to this field). So we access + * this field in this object under synchronization ({@link #igniteMonitor} serves as the monitor). + */ + private @Nullable IgniteImpl ignite; + + private final Object igniteMonitor = new Object(); + + /** + * Lock used to make sure user operations don't see Ignite instances in detached state (which might occur due to a restart) + * and that user operations linearize wrt detach/attach pairs (due to restarts). + */ + private final IgniteAttachmentLock attachmentLock; + + private final Ignite publicIgnite; private volatile @Nullable CompletableFuture<Void> joinFuture; + private final Object restartOrShutdownMonitor = new Object(); + + /** + * Used to make sure restart and shutdown requests are serviced sequentially. Review Comment: Please indicate that it guarded by `restartOrShutdownMonitor`. ########## modules/runner/src/main/java/org/apache/ignite/internal/restart/RefCache.java: ########## @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.restart; + +import java.util.function.Function; +import org.apache.ignite.Ignite; + +/** + * Cache for a reference obtained from some {@link Ignite} instance. Keeps the returned reference aligned with current Ignite + * (i.e. if a restart happened, then the object is recreated before being returned). + */ +class RefCache<T> { + private final Function<Ignite, T> supplier; + + // TODO: IGNITE-23005 - do not hold references to components of a stopped Ignite forever. + private IgniteAware<T> value; + + RefCache(Ignite initialIgnite, Function<Ignite, T> supplier) { + value = new IgniteAware<>(supplier.apply(initialIgnite), initialIgnite); + this.supplier = supplier; + } + + /** + * Returns an instance corresponding to the given {@link Ignite} instance. + * + * <p>This method must ALWAYS be called under the attachment lock. + * + * @param ignite Ignite instance for which to obtain an object. + */ + public T actualFor(Ignite ignite) { + IgniteAware<T> igniteAware = this.value; + + if (igniteAware == null || igniteAware.ignite() != ignite) { Review Comment: How `igniteAware` can be `null` ? ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/AsyncApiOperation.java: ########## @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.FULL_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.KEY_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.TEST_TABLE_NAME; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.VALUE_TUPLE; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import org.apache.ignite.internal.streamer.SimplePublisher; +import org.apache.ignite.internal.table.partition.HashPartition; +import org.apache.ignite.table.Tuple; +import org.apache.ignite.table.mapper.Mapper; + +/** + * Asynchronous API operation. + */ +enum AsyncApiOperation { + TABLES_TABLES(refs -> refs.tables.tablesAsync()), + TABLES_TABLE(refs -> refs.tables.tableAsync(TEST_TABLE_NAME)), + + KVVIEW_GET(refs -> refs.kvView.getAsync(null, KEY_TUPLE)), + KVVIEW_GET_OR_DEFAULT(refs -> refs.kvView.getOrDefaultAsync(null, KEY_TUPLE, null)), + KVVIEW_GET_ALL(refs -> refs.kvView.getAllAsync(null, List.of(KEY_TUPLE))), + KVVIEW_CONTAINS(refs -> refs.kvView.containsAsync(null, KEY_TUPLE)), + KVVIEW_CONTAINS_ALL(refs -> refs.kvView.containsAllAsync(null, List.of(KEY_TUPLE))), + KVVIEW_PUT(refs -> refs.kvView.putAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_PUT_ALL(refs -> refs.kvView.putAllAsync(null, Map.of(KEY_TUPLE, VALUE_TUPLE))), + KVVIEW_GET_AND_PUT(refs -> refs.kvView.getAndPutAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_PUT_IF_ABSENT(refs -> refs.kvView.putIfAbsentAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_REMOVE(refs -> refs.kvView.removeAsync(null, KEY_TUPLE)), + KVVIEW_REMOVE_EXACT(refs -> refs.kvView.removeAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_REMOVE_ALL(refs -> refs.kvView.removeAllAsync(null, List.of(KEY_TUPLE))), + KVVIEW_GET_AND_REMOVE(refs -> refs.kvView.getAndRemoveAsync(null, KEY_TUPLE)), + KVVIEW_REPLACE(refs -> refs.kvView.replaceAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_REPLACE_EXACT(refs -> refs.kvView.replaceAsync(null, KEY_TUPLE, VALUE_TUPLE, VALUE_TUPLE)), + KVVIEW_GET_AND_REPLACE(refs -> refs.kvView.getAndReplaceAsync(null, KEY_TUPLE, VALUE_TUPLE)), + VKVIEW_STREAM_DATA(refs -> { Review Comment: Maybe `KV_VIEW` not `VKVIEW`? ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/AsyncApiOperation.java: ########## @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.FULL_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.KEY_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.TEST_TABLE_NAME; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.VALUE_TUPLE; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import org.apache.ignite.internal.streamer.SimplePublisher; +import org.apache.ignite.internal.table.partition.HashPartition; +import org.apache.ignite.table.Tuple; +import org.apache.ignite.table.mapper.Mapper; + +/** + * Asynchronous API operation. + */ +enum AsyncApiOperation { + TABLES_TABLES(refs -> refs.tables.tablesAsync()), + TABLES_TABLE(refs -> refs.tables.tableAsync(TEST_TABLE_NAME)), + + KVVIEW_GET(refs -> refs.kvView.getAsync(null, KEY_TUPLE)), Review Comment: Let's make `KVVIEW` -> `KV_VIEW` for all constants. ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/AsyncApiOperation.java: ########## @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.FULL_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.KEY_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.TEST_TABLE_NAME; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.VALUE_TUPLE; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import org.apache.ignite.internal.streamer.SimplePublisher; +import org.apache.ignite.internal.table.partition.HashPartition; +import org.apache.ignite.table.Tuple; +import org.apache.ignite.table.mapper.Mapper; + +/** + * Asynchronous API operation. + */ +enum AsyncApiOperation { + TABLES_TABLES(refs -> refs.tables.tablesAsync()), + TABLES_TABLE(refs -> refs.tables.tableAsync(TEST_TABLE_NAME)), + + KVVIEW_GET(refs -> refs.kvView.getAsync(null, KEY_TUPLE)), + KVVIEW_GET_OR_DEFAULT(refs -> refs.kvView.getOrDefaultAsync(null, KEY_TUPLE, null)), + KVVIEW_GET_ALL(refs -> refs.kvView.getAllAsync(null, List.of(KEY_TUPLE))), + KVVIEW_CONTAINS(refs -> refs.kvView.containsAsync(null, KEY_TUPLE)), + KVVIEW_CONTAINS_ALL(refs -> refs.kvView.containsAllAsync(null, List.of(KEY_TUPLE))), + KVVIEW_PUT(refs -> refs.kvView.putAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_PUT_ALL(refs -> refs.kvView.putAllAsync(null, Map.of(KEY_TUPLE, VALUE_TUPLE))), + KVVIEW_GET_AND_PUT(refs -> refs.kvView.getAndPutAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_PUT_IF_ABSENT(refs -> refs.kvView.putIfAbsentAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_REMOVE(refs -> refs.kvView.removeAsync(null, KEY_TUPLE)), + KVVIEW_REMOVE_EXACT(refs -> refs.kvView.removeAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_REMOVE_ALL(refs -> refs.kvView.removeAllAsync(null, List.of(KEY_TUPLE))), + KVVIEW_GET_AND_REMOVE(refs -> refs.kvView.getAndRemoveAsync(null, KEY_TUPLE)), + KVVIEW_REPLACE(refs -> refs.kvView.replaceAsync(null, KEY_TUPLE, VALUE_TUPLE)), + KVVIEW_REPLACE_EXACT(refs -> refs.kvView.replaceAsync(null, KEY_TUPLE, VALUE_TUPLE, VALUE_TUPLE)), + KVVIEW_GET_AND_REPLACE(refs -> refs.kvView.getAndReplaceAsync(null, KEY_TUPLE, VALUE_TUPLE)), + VKVIEW_STREAM_DATA(refs -> { + CompletableFuture<?> future; + try (var publisher = new SimplePublisher<Entry<Tuple, Tuple>>()) { + future = refs.kvView.streamData(publisher, null); + publisher.submit(Map.entry(KEY_TUPLE, VALUE_TUPLE)); + } + return future; + }), + KVVIEW_QUERY(refs -> refs.kvView.queryAsync(null, null)), + KVVIEW_QUERY_WITH_INDEX(refs -> refs.kvView.queryAsync(null, null, null)), + KVVIEW_QUERY_WITH_OPTIONS(refs -> refs.kvView.queryAsync(null, null, null, null)), + + TYPED_KVVIEW_GET_NULLABLE(refs -> refs.typedKvView.getNullableAsync(null, 1)), + TYPED_KVVIEW_GET_NULLABLE_AND_PUT(refs -> refs.typedKvView.getNullableAndPutAsync(null, 1, "one")), + TYPED_KVVIEW_GET_NULLABLE_AND_REMOVE(refs -> refs.typedKvView.getNullableAndRemoveAsync(null, 1)), + TYPED_KVVIEW_GET_NULLABLE_AND_REPLACE(refs -> refs.typedKvView.getNullableAndReplaceAsync(null, 1, "one")), + + MAPPED_KVVIEW_GET(refs -> refs.mappedKvView.getAsync(null, 1)), + + RECORDVIEW_GET(refs -> refs.recordView.getAsync(null, KEY_TUPLE)), + RECORDVIEW_GET_ALL(refs -> refs.recordView.getAllAsync(null, List.of(KEY_TUPLE))), + RECORDVIEW_CONTAINS(refs -> refs.recordView.containsAsync(null, KEY_TUPLE)), + RECORDVIEW_CONTAINS_ALL(refs -> refs.recordView.containsAllAsync(null, List.of(KEY_TUPLE))), + RECORDVIEW_UPSERT(refs -> refs.recordView.upsertAsync(null, FULL_TUPLE)), + RECORDVIEW_UPSERT_ALL(refs -> refs.recordView.upsertAllAsync(null, List.of(FULL_TUPLE))), + RECORDVIEW_GET_AND_UPSERT(refs -> refs.recordView.getAndUpsertAsync(null, FULL_TUPLE)), + RECORDVIEW_INSERT(refs -> refs.recordView.insertAsync(null, FULL_TUPLE)), + RECORDVIEW_INSERT_ALL(refs -> refs.recordView.insertAllAsync(null, List.of(FULL_TUPLE))), + RECORDVIEW_REPLACE(refs -> refs.recordView.replaceAsync(null, FULL_TUPLE)), + RECORDVIEW_REPLACE_EXACT(refs -> refs.recordView.replaceAsync(null, FULL_TUPLE, FULL_TUPLE)), + RECORDVIEW_GET_AND_REPLACE(refs -> refs.recordView.getAndReplaceAsync(null, FULL_TUPLE)), + RECORDVIEW_DELETE(refs -> refs.recordView.deleteAsync(null, KEY_TUPLE)), + RECORDVIEW_DELETE_EXACT(refs -> refs.recordView.deleteExactAsync(null, FULL_TUPLE)), + RECORDVIEW_GET_AND_DELETE(refs -> refs.recordView.getAndDeleteAsync(null, KEY_TUPLE)), + RECORDVIEW_DELETE_ALL(refs -> refs.recordView.deleteAllAsync(null, List.of(KEY_TUPLE))), + RECORDVIEW_DELETE_ALL_EXACT(refs -> refs.recordView.deleteAllExactAsync(null, List.of(FULL_TUPLE))), + RECORDVIEW_STREAM_DATA(refs -> { + CompletableFuture<?> future; + try (var publisher = new SimplePublisher<Tuple>()) { + future = refs.recordView.streamData(publisher, null); + publisher.submit(FULL_TUPLE); + } + return future; + }), + RECORDVIEW_QUERY(refs -> refs.recordView.queryAsync(null, null)), + RECORDVIEW_QUERY_WITH_INDEX(refs -> refs.recordView.queryAsync(null, null, null)), + RECORDVIEW_QUERY_WITH_OPTIONS(refs -> refs.recordView.queryAsync(null, null, null, null)), + + TYPED_RECORDVIEW_GET(refs -> refs.typedRecordView.getAsync(null, new Record(1, ""))), + + MAPPED_RECORDVIEW_GET(refs -> refs.mappedRecordView.getAsync(null, new Record(1, ""))), + + PARTITION_MANAGER_PRIMARY_REPLICA(refs -> refs.partitionManager.primaryReplicaAsync(new HashPartition(0))), + PARTITION_MANAGER_PRIMARY_REPLICAS(refs -> refs.partitionManager.primaryReplicasAsync()), + PARTITION_MANAGER_PARTITION_ASYNC_BY_KEY(refs -> refs.partitionManager.partitionAsync(1, Mapper.of(Integer.class))), + PARTITION_MANAGER_PARTITION_ASYNC_BY_TUPLE(refs -> refs.partitionManager.partitionAsync(KEY_TUPLE)); Review Comment: Maybe without the `ASYNC` in the class name and so it is indicated. ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/SyncApiOperation.java: ########## @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.FULL_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.KEY_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.TEST_TABLE_NAME; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.VALUE_TUPLE; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.apache.ignite.table.mapper.Mapper; + +/** + * Synchronous API operation. + */ +@SuppressWarnings("resource") +enum SyncApiOperation { + IGNITE_NAME(refs -> refs.ignite.name()), + IGNITE_TABLES(refs -> refs.ignite.tables()), + IGNITE_TRANSACTIONS(refs -> refs.ignite.transactions()), + IGNITE_SQL(refs -> refs.ignite.sql()), + IGNITE_COMPUTE(refs -> refs.ignite.compute()), + IGNITE_CLUSTER_NODES(refs -> refs.ignite.clusterNodes()), + IGNITE_CATALOG(refs -> refs.ignite.catalog()), + + TABLES_TABLES(refs -> refs.tables.tables()), + TABLES_TABLE(refs -> refs.tables.table(TEST_TABLE_NAME)), + + TABLE_NAME(refs -> refs.table.name()), + TABLE_KVVIEW(refs -> refs.table.keyValueView()), + TABLE_TYPED_KVVIEW(refs -> refs.table.keyValueView(Integer.class, String.class)), + TABLE_MAPPED_KVVIEW(refs -> refs.table.keyValueView(Mapper.of(Integer.class), Mapper.of(String.class))), + TABLE_RECORDVIEW(refs -> refs.table.recordView()), Review Comment: Let's make `RECORDVIEW` -> `RECORD_VIEW` for all constants. ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/SyncApiOperation.java: ########## @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.FULL_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.KEY_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.TEST_TABLE_NAME; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.VALUE_TUPLE; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.apache.ignite.table.mapper.Mapper; + +/** + * Synchronous API operation. + */ +@SuppressWarnings("resource") +enum SyncApiOperation { + IGNITE_NAME(refs -> refs.ignite.name()), + IGNITE_TABLES(refs -> refs.ignite.tables()), + IGNITE_TRANSACTIONS(refs -> refs.ignite.transactions()), + IGNITE_SQL(refs -> refs.ignite.sql()), + IGNITE_COMPUTE(refs -> refs.ignite.compute()), + IGNITE_CLUSTER_NODES(refs -> refs.ignite.clusterNodes()), + IGNITE_CATALOG(refs -> refs.ignite.catalog()), + + TABLES_TABLES(refs -> refs.tables.tables()), + TABLES_TABLE(refs -> refs.tables.table(TEST_TABLE_NAME)), + + TABLE_NAME(refs -> refs.table.name()), + TABLE_KVVIEW(refs -> refs.table.keyValueView()), Review Comment: Let's make `KVVIEW` -> `KV_VIEW` for all constants. ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/ItInProcessRestartTest.java: ########## @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static java.util.concurrent.CompletableFuture.runAsync; +import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.ignite.Ignite; +import org.apache.ignite.internal.ClusterPerTestIntegrationTest; +import org.apache.ignite.internal.testframework.WorkDirectoryExtension; +import org.apache.ignite.table.KeyValueView; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(WorkDirectoryExtension.class) +class ItInProcessRestartTest extends ClusterPerTestIntegrationTest { + @Override + protected int initialNodes() { + return 1; + } + + @Test + void restarts() { + IgniteServerImpl server = (IgniteServerImpl) cluster.server(0); + + assertThat(server.restartAsync(), willCompleteSuccessfully()); + } + + /** + * Makes sure that operations happening during a restart finish successfully. + */ + @Test + void restartIsTransparent() { + Ignite ignite = node(0); + + ignite.sql().executeScript("CREATE TABLE test (id INT PRIMARY KEY, val VARCHAR)"); + KeyValueView<Integer, String> kvView = ignite.tables().table("test").keyValueView(Integer.class, String.class); + + CompletableFuture<Void> insertedSomething = new CompletableFuture<>(); + + AtomicBoolean restarted = new AtomicBoolean(false); + AtomicInteger lastInsertedId = new AtomicInteger(); + + CompletableFuture<Void> putsFuture = runAsync(() -> { + for (int i = 0; !restarted.get(); i++) { + kvView.put(null, i, "value"); + + lastInsertedId.set(i); + insertedSomething.complete(null); + } + }); + + IgniteServerImpl server = (IgniteServerImpl) cluster.server(0); + CompletableFuture<Void> restartedFuture = insertedSomething.thenCompose(unused -> server.restartAsync()) + .whenComplete((res, ex) -> restarted.set(true)); + assertThat(restartedFuture, willCompleteSuccessfully()); + + assertThat(putsFuture, willCompleteSuccessfully()); + + for (int i = 0; i < lastInsertedId.get(); i++) { + assertThat(kvView.get(null, i), is(notNullValue())); Review Comment: I would do a check on the values to make sure the correct data is returned after a restart. ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/References.java: ########## @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteServer; +import org.apache.ignite.table.IgniteTables; +import org.apache.ignite.table.KeyValueView; +import org.apache.ignite.table.RecordView; +import org.apache.ignite.table.Table; +import org.apache.ignite.table.Tuple; +import org.apache.ignite.table.mapper.Mapper; +import org.apache.ignite.table.partition.PartitionManager; + +/** + * References to API objects extracted from an {@link IgniteServer} instance. + */ +class References { + final Ignite ignite; + + final IgniteTables tables; + + final Table table; // From table(). + final Table tableFromTableAsync; + final Table tableFromTables; + final Table tableFromTablesAsync; + + final KeyValueView<Tuple, Tuple> kvView; + final KeyValueView<Integer, String> typedKvView; + final KeyValueView<Integer, String> mappedKvView; + + final RecordView<Tuple> recordView; + final RecordView<Record> typedRecordView; + final RecordView<Record> mappedRecordView; + + final PartitionManager partitionManager; + + References(IgniteServer server) throws Exception { + ignite = server.api(); + + tables = ignite.tables(); + table = tables.table(ApiReferencesTestUtils.TEST_TABLE_NAME); + tableFromTableAsync = tables.tableAsync(ApiReferencesTestUtils.TEST_TABLE_NAME).get(); Review Comment: I suggest not to wait forever. ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/ItInProcessRestartTest.java: ########## @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static java.util.concurrent.CompletableFuture.runAsync; +import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.ignite.Ignite; +import org.apache.ignite.internal.ClusterPerTestIntegrationTest; +import org.apache.ignite.internal.testframework.WorkDirectoryExtension; +import org.apache.ignite.table.KeyValueView; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(WorkDirectoryExtension.class) +class ItInProcessRestartTest extends ClusterPerTestIntegrationTest { + @Override + protected int initialNodes() { + return 1; + } + + @Test + void restarts() { + IgniteServerImpl server = (IgniteServerImpl) cluster.server(0); + + assertThat(server.restartAsync(), willCompleteSuccessfully()); + } + + /** + * Makes sure that operations happening during a restart finish successfully. + */ + @Test + void restartIsTransparent() { + Ignite ignite = node(0); + + ignite.sql().executeScript("CREATE TABLE test (id INT PRIMARY KEY, val VARCHAR)"); + KeyValueView<Integer, String> kvView = ignite.tables().table("test").keyValueView(Integer.class, String.class); + + CompletableFuture<Void> insertedSomething = new CompletableFuture<>(); Review Comment: Suggestion: you can use `var`. ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/SyncApiOperation.java: ########## @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.FULL_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.KEY_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.TEST_TABLE_NAME; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.VALUE_TUPLE; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.apache.ignite.table.mapper.Mapper; + +/** + * Synchronous API operation. + */ +@SuppressWarnings("resource") +enum SyncApiOperation { + IGNITE_NAME(refs -> refs.ignite.name()), Review Comment: Maybe without `IGNITE_`? ########## modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteServerImpl.java: ########## @@ -82,10 +86,33 @@ public class IgniteServerImpl implements IgniteServer { private final ClassLoader classLoader; - private volatile @Nullable IgniteImpl instance; + private final Executor asyncContinuationExecutor; + + /** Current Ignite instance. This field is not volatile to make hot path accesses from IgniteReference and other references + * faster (they always happen under a read lock, which guarantees visibility of changes to this field). So we access + * this field in this object under synchronization ({@link #igniteMonitor} serves as the monitor). + */ + private @Nullable IgniteImpl ignite; + + private final Object igniteMonitor = new Object(); Review Comment: I thought the classic name for such objects was "igniteMux". ########## modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteServerImpl.java: ########## @@ -281,9 +386,11 @@ private static void ackBanner() { private static void sync(CompletableFuture<Void> future) { try { - future.join(); - } catch (CompletionException e) { + future.get(); Review Comment: Can you explain the change? ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/ItShutDownServerApiReferencesTest.java: ########## @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.ignite.internal.testframework.asserts.CompletableFutureAssert.assertWillThrow; +import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.ignite.internal.ClusterPerClassIntegrationTest; +import org.apache.ignite.lang.IgniteException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +/** + * Makes sure that references to API objects obtained in embedded mode stop functioning after the node gets shut down. + */ +class ItShutDownServerApiReferencesTest extends ClusterPerClassIntegrationTest { + private static IgniteServerImpl server; + + private static References beforeShutdown; + + @Override + protected int initialNodes() { + return 1; + } + + @BeforeAll + void init() throws Exception { + server = (IgniteServerImpl) CLUSTER.server(0); + + server.api().sql().executeScript("CREATE TABLE test (id INT PRIMARY KEY, val VARCHAR)"); + + beforeShutdown = new References(server); + + assertThat(server.shutdownAsync(), willCompleteSuccessfully()); + } + + @ParameterizedTest + @EnumSource(SyncApiOperation.class) + void syncOperationsThrowAfterShutdown(SyncApiOperation operation) { + IgniteException ex = assertThrows(IgniteException.class, () -> operation.execute(beforeShutdown)); Review Comment: Maybe use `org.apache.ignite.internal.testframework.IgniteTestUtils#assertThrows` ? ########## modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteServerImpl.java: ########## @@ -82,10 +86,33 @@ public class IgniteServerImpl implements IgniteServer { private final ClassLoader classLoader; - private volatile @Nullable IgniteImpl instance; + private final Executor asyncContinuationExecutor; + + /** Current Ignite instance. This field is not volatile to make hot path accesses from IgniteReference and other references + * faster (they always happen under a read lock, which guarantees visibility of changes to this field). So we access + * this field in this object under synchronization ({@link #igniteMonitor} serves as the monitor). + */ + private @Nullable IgniteImpl ignite; Review Comment: I noticed that we only read and write in this field, why not replace it with `volatile`? ########## modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteServerImpl.java: ########## @@ -82,10 +86,33 @@ public class IgniteServerImpl implements IgniteServer { private final ClassLoader classLoader; - private volatile @Nullable IgniteImpl instance; + private final Executor asyncContinuationExecutor; + + /** Current Ignite instance. This field is not volatile to make hot path accesses from IgniteReference and other references + * faster (they always happen under a read lock, which guarantees visibility of changes to this field). So we access + * this field in this object under synchronization ({@link #igniteMonitor} serves as the monitor). + */ + private @Nullable IgniteImpl ignite; + + private final Object igniteMonitor = new Object(); + + /** + * Lock used to make sure user operations don't see Ignite instances in detached state (which might occur due to a restart) + * and that user operations linearize wrt detach/attach pairs (due to restarts). + */ + private final IgniteAttachmentLock attachmentLock; + + private final Ignite publicIgnite; private volatile @Nullable CompletableFuture<Void> joinFuture; + private final Object restartOrShutdownMonitor = new Object(); Review Comment: Same about mux ########## modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteServerImpl.java: ########## @@ -82,10 +86,33 @@ public class IgniteServerImpl implements IgniteServer { private final ClassLoader classLoader; - private volatile @Nullable IgniteImpl instance; + private final Executor asyncContinuationExecutor; + + /** Current Ignite instance. This field is not volatile to make hot path accesses from IgniteReference and other references Review Comment: Please add new line. ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/SyncApiOperation.java: ########## @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.FULL_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.KEY_TUPLE; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.TEST_TABLE_NAME; +import static org.apache.ignite.internal.app.ApiReferencesTestUtils.VALUE_TUPLE; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.apache.ignite.table.mapper.Mapper; + +/** + * Synchronous API operation. + */ +@SuppressWarnings("resource") +enum SyncApiOperation { + IGNITE_NAME(refs -> refs.ignite.name()), + IGNITE_TABLES(refs -> refs.ignite.tables()), + IGNITE_TRANSACTIONS(refs -> refs.ignite.transactions()), + IGNITE_SQL(refs -> refs.ignite.sql()), + IGNITE_COMPUTE(refs -> refs.ignite.compute()), + IGNITE_CLUSTER_NODES(refs -> refs.ignite.clusterNodes()), + IGNITE_CATALOG(refs -> refs.ignite.catalog()), + + TABLES_TABLES(refs -> refs.tables.tables()), + TABLES_TABLE(refs -> refs.tables.table(TEST_TABLE_NAME)), + + TABLE_NAME(refs -> refs.table.name()), + TABLE_KVVIEW(refs -> refs.table.keyValueView()), + TABLE_TYPED_KVVIEW(refs -> refs.table.keyValueView(Integer.class, String.class)), + TABLE_MAPPED_KVVIEW(refs -> refs.table.keyValueView(Mapper.of(Integer.class), Mapper.of(String.class))), + TABLE_RECORDVIEW(refs -> refs.table.recordView()), + TABLE_TYPED_RECORDVIEW(refs -> refs.table.recordView(Record.class)), + TABLE_MAPPED_RECORDVIEW(refs -> refs.table.recordView(Mapper.of(Record.class))), + TABLE_PARTITION_MANAGER(refs -> refs.table.partitionManager()), + + TABLE_FROM_TABLE_ASYNC_PUT(refs -> refs.tableFromTableAsync.keyValueView().put(null, KEY_TUPLE, VALUE_TUPLE)), Review Comment: Maybe dont use `ASYNC` it's a bit confusing from the class name. ########## modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteServerImpl.java: ########## @@ -188,13 +236,64 @@ public CompletableFuture<Void> waitForInitAsync() { return joinFuture; } + /** + * Restarts the node asynchronously. The {@link Ignite} instance obtained via {@link #api()} and objects acquired through it remain + * functional, but completion of calls to them might be delayed during the restart (that is, synchronous calls might take more time, + * while futures from asynchronous calls might take more time to complete). + * + * @return CompletableFuture that gets completed when the node startup has completed (either successfully or with an error). + */ + CompletableFuture<Void> restartAsync() { Review Comment: It looks like there might be a race here. While we started restarting the node, it is shutdown in the process and the first checks that are indicated here will already be passed. What about using not mutex but CAS on the `restartOrShutdownFuture` and each new operation will be performed after the previous one is completed and will thus be linearized and with the correct checks. ########## modules/runner/src/integrationTest/java/org/apache/ignite/internal/app/ItShutDownServerApiReferencesTest.java: ########## @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.app; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.ignite.internal.testframework.asserts.CompletableFutureAssert.assertWillThrow; +import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.ignite.internal.ClusterPerClassIntegrationTest; +import org.apache.ignite.lang.IgniteException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +/** + * Makes sure that references to API objects obtained in embedded mode stop functioning after the node gets shut down. + */ +class ItShutDownServerApiReferencesTest extends ClusterPerClassIntegrationTest { + private static IgniteServerImpl server; + + private static References beforeShutdown; + + @Override + protected int initialNodes() { + return 1; + } + + @BeforeAll + void init() throws Exception { + server = (IgniteServerImpl) CLUSTER.server(0); + + server.api().sql().executeScript("CREATE TABLE test (id INT PRIMARY KEY, val VARCHAR)"); + + beforeShutdown = new References(server); + + assertThat(server.shutdownAsync(), willCompleteSuccessfully()); + } + + @ParameterizedTest + @EnumSource(SyncApiOperation.class) + void syncOperationsThrowAfterShutdown(SyncApiOperation operation) { + IgniteException ex = assertThrows(IgniteException.class, () -> operation.execute(beforeShutdown)); + assertThat(ex.getMessage(), is("The node is already shut down.")); + } + + @ParameterizedTest + @EnumSource(AsyncApiOperation.class) + void asyncOperationsWorkAfterRestart(AsyncApiOperation operation) { + IgniteException ex = assertWillThrow(operation.execute(beforeShutdown), IgniteException.class, 10, SECONDS); Review Comment: Maybe use `org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher#willThrow(org.hamcrest.Matcher<? extends java.lang.Exception>, int, java.util.concurrent.TimeUnit, java.lang.String)` ? ########## modules/runner/src/main/java/org/apache/ignite/internal/restart/RestartProofApiObject.java: ########## @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.restart; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; +import org.apache.ignite.Ignite; + +/** + * Base for references to API objects under a swappable {@link Ignite}. + */ +abstract class RestartProofApiObject<T> { Review Comment: What does `Proof` mean in the class name? -- 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: notifications-unsubscr...@ignite.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org