Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: possible race condition on EXCHANGE TABLES query #61135

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
558d764
add storage cache to context and use it during query analysis and exe…
yakov-olkhovskiy Mar 10, 2024
f8b4ef0
test added, comment fixed
yakov-olkhovskiy Mar 11, 2024
2de26fa
style check typo
yakov-olkhovskiy Mar 11, 2024
2f051c7
fix for query having no query context
yakov-olkhovskiy Mar 11, 2024
067a8c8
move to DatabaseCatalog
yakov-olkhovskiy Mar 11, 2024
2825656
fix
yakov-olkhovskiy Mar 11, 2024
064eca6
use weak_ptr
yakov-olkhovskiy Mar 12, 2024
c66d8e1
use StorageID instead of database.table
yakov-olkhovskiy Mar 12, 2024
792a298
Merge remote-tracking branch 'origin/master' into fix-exchange-tables
yakov-olkhovskiy Mar 13, 2024
b24629d
Merge remote-tracking branch 'origin/master' into fix-exchange-tables
yakov-olkhovskiy Mar 16, 2024
5955cc5
add no-ordinary-database tag
yakov-olkhovskiy Mar 20, 2024
e121665
cache storage by UUID
yakov-olkhovskiy Mar 22, 2024
190823f
cleanup
yakov-olkhovskiy Mar 22, 2024
4eeff80
Merge branch 'master' into fix-exchange-tables
yakov-olkhovskiy Mar 22, 2024
c67a898
fix
yakov-olkhovskiy Mar 22, 2024
0eae42a
better argument name
yakov-olkhovskiy Mar 30, 2024
242dc60
Merge branch 'master' into fix-exchange-tables
yakov-olkhovskiy Apr 2, 2024
57627ec
Merge branch 'master' into fix-exchange-tables
yakov-olkhovskiy Apr 3, 2024
856cb21
add mutex to storage_cache
yakov-olkhovskiy Apr 6, 2024
1bd25d0
Merge branch 'master' into fix-exchange-tables
yakov-olkhovskiy Apr 6, 2024
c84b702
fix clang tidy
yakov-olkhovskiy Apr 6, 2024
f0d55ea
Merge branch 'master' into fix-exchange-tables
yakov-olkhovskiy Apr 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/Interpreters/Context.cpp
Expand Up @@ -904,6 +904,21 @@ String Context::getFilesystemCacheUser() const
return shared->filesystem_cache_user;
}

StoragePtr Context::getOrCacheStorage(const StorageID & id, std::function<StoragePtr()> f) const
{
if (auto storage = storage_cache.find(id); storage != storage_cache.end())
{
if (auto storage_ptr = storage->second.lock(); storage_ptr)
return storage_ptr;
else
storage_cache.erase(storage);
}
auto storage = f();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider a better name

if (storage)
storage_cache.insert({id, storage});
return storage;
}

Strings Context::getWarnings() const
{
Strings common_warnings;
Expand Down
7 changes: 7 additions & 0 deletions src/Interpreters/Context.h
Expand Up @@ -33,6 +33,7 @@
#include <memory>
#include <mutex>
#include <optional>
#include <unordered_map>


namespace Poco::Net { class IPAddress; }
Expand Down Expand Up @@ -421,6 +422,10 @@ class ContextData
/// mutation tasks of one mutation executed against different parts of the same table.
PreparedSetsCachePtr prepared_sets_cache;

/// Cache for storages resolved during query processing - we want to use the same storages we have resolved on
/// analysis stage for the execution - storage can be changed by concurrently running 'EXCHANGE TABLES' query.
mutable std::unordered_map<StorageID, StorageWeakPtr, StorageID::DatabaseAndTableNameHash, StorageID::DatabaseAndTableNameEqual> storage_cache;

public:
/// Some counters for current query execution.
/// Most of them are workarounds and should be removed in the future.
Expand Down Expand Up @@ -515,6 +520,8 @@ class Context: public ContextData, public std::enable_shared_from_this<Context>
String getFilesystemCachesPath() const;
String getFilesystemCacheUser() const;

StoragePtr getOrCacheStorage(const StorageID & id, std::function<StoragePtr()> f) const;

/// A list of warnings about server configuration to place in `system.warnings` table.
Strings getWarnings() const;

Expand Down
12 changes: 8 additions & 4 deletions src/Interpreters/DatabaseCatalog.cpp
Expand Up @@ -949,15 +949,19 @@ bool DatabaseCatalog::isDictionaryExist(const StorageID & table_id) const
StoragePtr DatabaseCatalog::getTable(const StorageID & table_id, ContextPtr local_context) const
{
std::optional<Exception> exc;
auto res = getTableImpl(table_id, local_context, &exc);
if (!res.second)
auto table = local_context->hasQueryContext() ?
local_context->getQueryContext()->getOrCacheStorage(table_id, [&](){ return getTableImpl(table_id, local_context, &exc).second; }) :
getTableImpl(table_id, local_context, &exc).second;
if (!table)
throw Exception(*exc);
return res.second;
return table;
}

StoragePtr DatabaseCatalog::tryGetTable(const StorageID & table_id, ContextPtr local_context) const
{
return getTableImpl(table_id, local_context, nullptr).second;
return local_context->hasQueryContext() ?
local_context->getQueryContext()->getOrCacheStorage(table_id, [&](){ return getTableImpl(table_id, local_context, nullptr).second; }) :
getTableImpl(table_id, local_context, nullptr).second;
}

DatabaseAndTable DatabaseCatalog::getDatabaseAndTable(const StorageID & table_id, ContextPtr local_context) const
Expand Down
1 change: 1 addition & 0 deletions src/Storages/IStorage_fwd.h
Expand Up @@ -12,6 +12,7 @@ class IStorage;

using ConstStoragePtr = std::shared_ptr<const IStorage>;
using StoragePtr = std::shared_ptr<IStorage>;
using StorageWeakPtr = std::weak_ptr<IStorage>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not allowed to use std::weak_ptr<IStorage> because we rely on std::shared_ptr<IStorage>::unique()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of saving StoragePtr, we can save StorageID with resolved UUID

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's exactly the point to not keep shared_ptr here - since on HTTP requests session can be preserved it would block database from DROP - making it weak_ptr doesn't block it and we don't need to keep it cached beyond session so it should be safe

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a race condition

using Tables = std::map<String, StoragePtr>;

}
Empty file.
26 changes: 26 additions & 0 deletions tests/queries/0_stateless/03007_exchange_tables_race.sh
@@ -0,0 +1,26 @@
#!/usr/bin/env bash

set -e

CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CURDIR"/../shell_config.sh

${CLICKHOUSE_CLIENT} --multiquery <<EOF
DROP TABLE IF EXISTS tbl_03007_1;
DROP TABLE IF EXISTS tbl_03007_2;
CREATE TABLE tbl_03007_1 (n Float64) ENGINE=Memory;
CREATE TABLE tbl_03007_2 (n Int256) ENGINE=Memory;
EOF

for _ in {1..50}; do
(! ${CLICKHOUSE_CLIENT} --query "SELECT n * 0.123 FROM (SELECT * FROM tbl_03007_1)" 2>&1 | grep LOGICAL_ERROR) &
${CLICKHOUSE_CLIENT} --query "EXCHANGE TABLES tbl_03007_1 AND tbl_03007_2" &
done

wait

${CLICKHOUSE_CLIENT} --multiquery <<EOF
DROP TABLE IF EXISTS tbl_03007_1;
DROP TABLE IF EXISTS tbl_03007_2;
EOF