Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@
import com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan;
import com.apple.foundationdb.subspace.Subspace;
import com.apple.foundationdb.tuple.Tuple;
import com.apple.foundationdb.tuple.TupleHelpers;

Check notice on line 73 in fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreBase.java

View workflow job for this annotation

GitHub Actions / coverage

File coverage: 87.6% (184/210 lines) | Changed lines: N/A (no executable lines)
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.protobuf.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -2488,6 +2489,36 @@
@Nonnull
BaseBuilder<M, R> setStateCacheabilityOnOpen(@Nonnull FDBRecordStore.StateCacheabilityOnOpen stateCacheabilityOnOpen);

/**
* Whether concurrency management is disabled. See {@link #setDisableConcurrencyManagement(boolean)} for
* more details.
*
* @return whether concurrency management is disabled
* @see com.apple.foundationdb.record.provider.foundationdb.concurrency.StoreConcurrencyManager
* @see #setDisableConcurrencyManagement(boolean)
*/
@API(API.Status.EXPERIMENTAL)
boolean isConcurrencyManagementDisabled();

/**
* Set whether to disable concurrency management. By default, the record store will use an
* {@link com.apple.foundationdb.record.provider.foundationdb.concurrency.StoreConcurrencyManager}
* to manage concurrent operations on the store. For example, the concurrency manager makes sure that
* concurrent operations that save the same record (in the same transaction) do not interfere with each
* other. If this is disabled, then those guardrails are removed, and it is the caller's responsibility
* to ensure that conflicting operations are not executed at the same time. In general, it is not
* recommended to run with this mode. The user should only opt in to this if they notice problems
* (e.g., the lock manager is too slow or something about their workflow causes a deadlock).
*
* @param disableConcurrencyManagement whether to disable concurrency management
* @return this builder
* @see com.apple.foundationdb.record.provider.foundationdb.concurrency.StoreConcurrencyManager
*/
@API(API.Status.EXPERIMENTAL)
@CanIgnoreReturnValue
@Nonnull
BaseBuilder<M, R> setDisableConcurrencyManagement(boolean disableConcurrencyManagement);

/**
* Make a copy of this builder.
* This can be used to share enough of the state to connect to the same record store several times in different transactions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,19 @@
untypedStoreBuilder.setStateCacheabilityOnOpen(stateCacheabilityOnOpen);
return this;
}

Check notice on line 580 in fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBTypedRecordStore.java

View workflow job for this annotation

GitHub Actions / coverage

File coverage: 45.0% (58/129 lines) | Changed lines: 100.0% (3/3 lines)
@Override
public boolean isConcurrencyManagementDisabled() {
return untypedStoreBuilder.isConcurrencyManagementDisabled();
}

@Nonnull
@Override
public Builder<M> setDisableConcurrencyManagement(final boolean disableConcurrencyManagement) {
untypedStoreBuilder.setDisableConcurrencyManagement(disableConcurrencyManagement);
return this;
}

@Nullable
@Override
public String getBypassFullStoreLockReason() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*

Check notice on line 1 in fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/concurrency/FDBRecordStoreConcurrencyManager.java

View workflow job for this annotation

GitHub Actions / coverage

File coverage: 100.0% (16/16 lines) | Changed lines: 100.0% (16/16 lines)
* FDBRecordStoreConcurrencyManager.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2026 Apple Inc. and the FoundationDB project authors
*
* Licensed 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 com.apple.foundationdb.record.provider.foundationdb.concurrency;

import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.record.locking.LockIdentifier;
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext;
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreKeyspace;
import com.apple.foundationdb.record.provider.foundationdb.SubspaceProvider;
import com.apple.foundationdb.subspace.Subspace;
import com.apple.foundationdb.tuple.Tuple;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

/**
* Default implementation of the {@link StoreConcurrencyManager}. It manages the locks used for
* read and write operations on behalf of the store.
*/
@API(API.Status.INTERNAL)
public final class FDBRecordStoreConcurrencyManager implements StoreConcurrencyManager {
@Nonnull
private final SubspaceProvider subspaceProvider;
@Nonnull
private final FDBRecordContext context;
@Nullable
private volatile Subspace recordSubspace;

public FDBRecordStoreConcurrencyManager(@Nonnull SubspaceProvider subspaceProvider, @Nonnull FDBRecordContext context) {
this.subspaceProvider = subspaceProvider;
this.context = context;
}

@Nonnull
private CompletableFuture<Subspace> getRecordsSubspaceAsync() {
Subspace cached = recordSubspace;
if (cached == null) {
// If we don't have a cached subspace, re-create it. We prefer this over using Suppliers::memoize
// so that subspace resolution throws a transient error, we don't memoize that future.
// It is possible that there are multiple subspace resolutions happening at the same time, but that's
// fine as they will all compute equivalent values, so it doesn't matter which one(s) win the race
// to set the cached subspace
return subspaceProvider.getSubspaceAsync(context).thenApply(storeSubspace -> {
final Subspace newRecordsSubspace = storeSubspace.subspace(Tuple.from(FDBRecordStoreKeyspace.RECORD.key()));
recordSubspace = newRecordsSubspace;
return newRecordsSubspace;
});
} else {
return CompletableFuture.completedFuture(cached);
}
}

@Nonnull
private CompletableFuture<LockIdentifier> lockIdentifierForRecord(@Nonnull Tuple primaryKey) {
return getRecordsSubspaceAsync()
.thenApply(recordsSubspace -> recordsSubspace.subspace(primaryKey))
.thenApply(LockIdentifier::new);
}

@Override
public <T> CompletableFuture<T> doWithRecordReadLock(@Nonnull final Tuple primaryKey, @Nonnull final Supplier<CompletableFuture<T>> operation) {
return lockIdentifierForRecord(primaryKey).thenCompose(id -> context.doWithReadLock(id, operation));
}

@Override
public <T> CompletableFuture<T> doWithRecordWriteLock(@Nonnull final Tuple primaryKey, @Nonnull final Supplier<CompletableFuture<T>> operation) {
return lockIdentifierForRecord(primaryKey).thenCompose(id -> context.doWithWriteLock(id, operation));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*

Check notice on line 1 in fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/concurrency/NoOpConcurrencyManager.java

View workflow job for this annotation

GitHub Actions / coverage

File coverage: 100.0% (4/4 lines) | Changed lines: 100.0% (4/4 lines)
* NoOpConcurrencyManager.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2026 Apple Inc. and the FoundationDB project authors
*
* Licensed 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 com.apple.foundationdb.record.provider.foundationdb.concurrency;

import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.tuple.Tuple;

import javax.annotation.Nonnull;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

/**
* Implementation of the {@link StoreConcurrencyManager} that does nothing. All operations are
* immediately allowed with no actual management of the concurrency occurring.
*/
@API(API.Status.INTERNAL)
public final class NoOpConcurrencyManager implements StoreConcurrencyManager {
@Nonnull
private static final NoOpConcurrencyManager INSTANCE = new NoOpConcurrencyManager();

private NoOpConcurrencyManager() {
// Singleton
}

@Override
public <T> CompletableFuture<T> doWithRecordReadLock(@Nonnull final Tuple primaryKey, @Nonnull final Supplier<CompletableFuture<T>> operation) {
return operation.get();
}

@Override
public <T> CompletableFuture<T> doWithRecordWriteLock(@Nonnull final Tuple primaryKey, @Nonnull final Supplier<CompletableFuture<T>> operation) {
return operation.get();
}

/**
* Retrieve the instance for this class. The {@link NoOpConcurrencyManager} is a singleton,
* so this returns the same instance every time.
*
* @return the singleton instance
*/
@Nonnull
public static NoOpConcurrencyManager instance() {
return INSTANCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*

Check notice on line 1 in fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/concurrency/StoreConcurrencyManager.java

View workflow job for this annotation

GitHub Actions / coverage

File coverage: N/A (no executable lines) | Changed lines: N/A (no executable lines)
* FDBRecordStoreConcurrencyManager.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2026 Apple Inc. and the FoundationDB project authors
*
* Licensed 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 com.apple.foundationdb.record.provider.foundationdb.concurrency;

import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.record.query.expressions.QueryComponent;
import com.apple.foundationdb.tuple.Tuple;
import com.google.protobuf.Message;

import javax.annotation.Nonnull;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

/**
* Interface for managing the concurrency within a given {@link com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore}.
* That class is created with an instance of this interface as a member, and it should route operations through
* appropriate methods.
*
* <p>
* This framework is a bit of a work in progress. It currently only offers basic protection for single record
* operations. That is, it prevents multiple operations from hitting issues when interleaving reads or writes
* to the same record, but it does not take any locks to prevent a concurrent read and a range operation like
* a {@link com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore#deleteRecordsWhere(QueryComponent) deleteRecordsWhere()}.
* Nor will it prevent concurrent writes to the database during a query. As the framework gets refined, this
* may change. As such, the adopter is still somewhat responsible for managing their concurrent accesses to
* the record store until some of these shortcomings are addressed.
* </p>
*
* <p>
* If the lock management causes problems (e.g., if managing the locks takes too many resources or if the
* introduction of the lock manager results in deadlocks), this can be disabled by invoking
* {@link com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase.BaseBuilder#setDisableConcurrencyManagement(boolean) setDisableConcurrencyManagement(true)}
* on the store's builder. This will switch the implementation to the {@link NoOpConcurrencyManager}, which
* runs all operations immediately without waiting for any lock.
* </p>
*
* @see com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase.BaseBuilder#setDisableConcurrencyManagement(boolean)
* @see FDBRecordStoreConcurrencyManager for the default implementation
* @see NoOpConcurrencyManager for an implementation that does nothing, allowing the user to opt-out if problems arise
*/
@API(API.Status.INTERNAL)
public sealed interface StoreConcurrencyManager permits NoOpConcurrencyManager, FDBRecordStoreConcurrencyManager {
/**
* Perform an operation with a shared lock covering a single record.
* This is applied on operations like {@link com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore#loadRecordAsync(Tuple) loadRecordAsync()}
* to ensure that the read does not see partial updates, e.g., one split point overwritten by a
* concurrent {@link com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore#saveRecordAsync(Message) saveRecordAsync()}.
* This will wait for any previously started writes to the record to finish before beginning the operation, and it will
* block any future writes to the record from beginning until this read has completed.
*
* @param primaryKey the primary key of the record being read
* @param operation an operation to execute
* @return a future that will complete when the operation has finished
* @param <T> the type returned by the operation
*/
<T> CompletableFuture<T> doWithRecordReadLock(@Nonnull Tuple primaryKey, @Nonnull Supplier<CompletableFuture<T>> operation);

/**
* Perform an operation with an exclusive lock covering a single record.
* This is applied on operations like {@link com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore#saveRecordAsync(Message) saveRecordAsync()}
* and {@link com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore#deleteRecordAsync(Tuple) deleteRecordAsync()}
* to ensure that the writes do not interfere with each other or with any concurrent reads.
* This will wait for any previously started operations to the record to finish before beginning, and it will
* block any future operations to the record from beginning until this write has completed.
*
* @param primaryKey the primary key of the record being written
* @param operation an operation to execute
* @return a future that will complete when the operation has finished
* @param <T> the type returned by the operation
*/
<T> CompletableFuture<T> doWithRecordWriteLock(@Nonnull Tuple primaryKey, @Nonnull Supplier<CompletableFuture<T>> operation);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* package-info.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2026 Apple Inc. and the FoundationDB project authors
*
* Licensed 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.
*/

/**
* Classes for managing concurrency of a store within a single transaction. For managing
* concurrency across different transactions, we generally rely on the FDB transaction
* resolver. That will fail any transaction that relies on stale information. However,
* within a transaction, the FDB client itself offers little help, and so locks need
* to be managed by the Record Layer framework itself. We also need to avoid taking normal
* locks over long-running operations, as that can block threads in the asynchronous thread pool.
* For that reason, we prefer using the {@link com.apple.foundationdb.record.locking.AsyncLock}
* abstraction, which returns a future that is made available only when a resource is
* ready. Each async lock is associated with a given resource key (that is, a
* {@link com.apple.foundationdb.record.locking.LockIdentifier}), and so the classes in
* this package are primarily present to ensure that we manage those locks the right way.
*
* @see com.apple.foundationdb.record.locking.AsyncLock
* @see com.apple.foundationdb.record.locking.LockRegistry
*/
package com.apple.foundationdb.record.provider.foundationdb.concurrency;
Loading
Loading