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
27 changes: 27 additions & 0 deletions src/main/java/io/supertokens/storage/postgresql/LockFailure.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* 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 io.supertokens.storage.postgresql;

public class LockFailure extends Exception {
public LockFailure() {
super("Failed to acquire advisory lock");
}

public LockFailure(String message) {
super(message);
}
}
3 changes: 2 additions & 1 deletion src/main/java/io/supertokens/storage/postgresql/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ public void initStorage(boolean shouldWait, List<TenantIdentifier> tenantIdentif
@Override
public <T> T startTransaction(TransactionLogic<T> logic)
throws StorageTransactionLogicException, StorageQueryException {
return startTransaction(logic, TransactionIsolationLevel.SERIALIZABLE);
return startTransaction(logic, TransactionIsolationLevel.READ_COMMITTED);
}

@Override
Expand Down Expand Up @@ -384,6 +384,7 @@ public <T> T startTransaction(TransactionLogic<T> logic, TransactionIsolationLev
// We could get here if the new logic hits a false negative,
// e.g., in case someone renamed constraints/tables
boolean isDeadlockException = actualException instanceof SQLTransactionRollbackException
|| actualException instanceof LockFailure
|| exceptionMessage.toLowerCase().contains("concurrent update")
|| exceptionMessage.toLowerCase().contains("concurrent delete")
|| exceptionMessage.toLowerCase().contains("the transaction might succeed if retried") ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -875,8 +875,12 @@ public static KeyValueInfo getKeyValue(Start start, TenantIdentifier tenantIdent
public static KeyValueInfo getKeyValue_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier,
String key)
throws SQLException, StorageQueryException {

io.supertokens.storage.postgresql.queries.Utils.takeAdvisoryLock(
con, tenantIdentifier.getAppId() + "~" + tenantIdentifier.getTenantId() + "~" + key);

String QUERY = "SELECT value, created_at_time FROM " + getConfig(start).getKeyValueTable()
+ " WHERE app_id = ? AND tenant_id = ? AND name = ? FOR UPDATE";
+ " WHERE app_id = ? AND tenant_id = ? AND name = ?";

return execute(con, QUERY, pst -> {
pst.setString(1, tenantIdentifier.getAppId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ public static void overwriteTenantConfig(Start start, TenantConfig tenantConfig)
Connection sqlCon = (Connection) con.getConnection();
{
try {
{
io.supertokens.storage.postgresql.queries.Utils.takeAdvisoryLock(
sqlCon, tenantConfig.tenantIdentifier.getConnectionUriDomain() + "~" + tenantConfig.tenantIdentifier.getAppId() + "~" + tenantConfig.tenantIdentifier.getTenantId());
}
{
String QUERY = "DELETE FROM " + getConfig(start).getTenantConfigsTable()
+ " WHERE connection_uri_domain = ? AND app_id = ? AND tenant_id = ?;";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,27 @@

package io.supertokens.storage.postgresql.queries;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.storage.postgresql.PreparedStatementValueSetter;
import static io.supertokens.storage.postgresql.QueryExecutorTemplate.execute;
import static io.supertokens.storage.postgresql.QueryExecutorTemplate.executeBatch;
import static io.supertokens.storage.postgresql.QueryExecutorTemplate.update;
import io.supertokens.storage.postgresql.Start;
import io.supertokens.storage.postgresql.config.Config;
import io.supertokens.storage.postgresql.utils.Utils;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static io.supertokens.storage.postgresql.QueryExecutorTemplate.*;
import static io.supertokens.storage.postgresql.config.Config.getConfig;
import io.supertokens.storage.postgresql.utils.Utils;

public class UserMetadataQueries {

Expand Down Expand Up @@ -121,6 +123,7 @@ public static void setMultipleUsersMetadatas_Transaction(Start start, Connection
public static JsonObject getUserMetadata_Transaction(Start start, Connection con, AppIdentifier appIdentifier,
String userId)
throws SQLException, StorageQueryException {
io.supertokens.storage.postgresql.queries.Utils.takeAdvisoryLock(con, appIdentifier.getAppId() + "~" + userId);
String QUERY = "SELECT user_metadata FROM " + getConfig(start).getUserMetadataTable()
+ " WHERE app_id = ? AND user_id = ? FOR UPDATE";
return execute(con, QUERY, pst -> {
Expand Down
53 changes: 53 additions & 0 deletions src/main/java/io/supertokens/storage/postgresql/queries/Utils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* 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 io.supertokens.storage.postgresql.queries;

import java.sql.Connection;
import java.sql.SQLException;

import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.storage.postgresql.LockFailure;
import static io.supertokens.storage.postgresql.QueryExecutorTemplate.execute;

public class Utils {

/**
* Acquires a PostgreSQL advisory lock using two string keys.
* Uses pg_try_advisory_xact_lock which is transaction-scoped (automatically released on commit/rollback).
*
* @param con The database connection (must be within a transaction)
* @param key Key for the lock (e.g., appId)
* @throws SQLException If a database error occurs
* @throws StorageQueryException If a query error occurs
* @throws LockFailure If the lock could not be acquired
*/
public static void takeAdvisoryLock(Connection con, String key)
throws SQLException, StorageQueryException {
String LOCK_QUERY = "SELECT pg_try_advisory_xact_lock(hashtext(?))";
boolean lockAcquired = execute(con, LOCK_QUERY, pst -> {
pst.setString(1, key);
}, result -> {
if (result.next()) {
return result.getBoolean(1);
}
return false;
});
if (!lockAcquired) {
throw new StorageQueryException(new LockFailure());
}
}
}
Loading