Skip to content

Commit 655db0e

Browse files
authored
Merge branch 'main' into codex/fix-tsan
2 parents 1b9a3ad + c097bb9 commit 655db0e

16 files changed

Lines changed: 332 additions & 73 deletions

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ option(PAIMON_ENABLE_AVRO "Whether to enable avro file format" ON)
5757
option(PAIMON_ENABLE_ORC "Whether to enable orc file format" ON)
5858
option(PAIMON_ENABLE_LANCE "Whether to enable lance file format" OFF)
5959
option(PAIMON_ENABLE_JINDO "Whether to enable jindo file system" OFF)
60+
option(PAIMON_ENABLE_NETWORK_TESTS
61+
"Whether to enable tests that access real remote services over the network" OFF)
6062
option(PAIMON_ENABLE_LUMINA "Whether to enable lumina vector index" OFF)
6163
option(PAIMON_ENABLE_LUCENE "Whether to enable lucene index" OFF)
6264
option(PAIMON_ENABLE_TANTIVY
@@ -70,6 +72,11 @@ endif()
7072
if(PAIMON_ENABLE_JINDO)
7173
add_definitions(-DPAIMON_ENABLE_JINDO)
7274
endif()
75+
if(PAIMON_ENABLE_NETWORK_TESTS)
76+
if(NOT PAIMON_BUILD_TESTS)
77+
message(FATAL_ERROR "PAIMON_ENABLE_NETWORK_TESTS requires PAIMON_BUILD_TESTS=ON")
78+
endif()
79+
endif()
7380
if(PAIMON_USE_CXX11_ABI)
7481
add_definitions(-D_GLIBCXX_USE_CXX11_ABI=1)
7582
else()

cmake_modules/DefineOptions.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
107107

108108
define_option(PAIMON_BUILD_TESTS "Build the Paimon googletest unit tests" OFF)
109109

110+
define_option(PAIMON_ENABLE_NETWORK_TESTS
111+
"Enable tests that access real remote services over the network" OFF)
112+
110113
define_option(PAIMON_BUILD_BENCHMARKS
111114
"Build the Paimon Google Benchmark performance benchmarks" OFF)
112115

include/paimon/fs/file_system.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class PAIMON_EXPORT InputStream : public Stream {
9898
/// @param callback The callback function to be invoked upon completion of the read operation.
9999
/// The callback will receive a Status object indicating the success or failure
100100
/// of the read operation.
101+
/// @note The caller must keep the input stream and buffer alive until the callback is invoked.
101102
virtual void ReadAsync(char* buffer, int64_t size, int64_t offset,
102103
std::function<void(Status)>&& callback) = 0;
103104

src/paimon/CMakeLists.txt

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -791,29 +791,39 @@ if(PAIMON_BUILD_TESTS)
791791
${TEST_STATIC_LINK_LIBS}
792792
${GTEST_LINK_TOOLCHAIN})
793793

794-
# jindo_utils_test only checks the in-memory status conversion and does not need an
795-
# OSS cluster, so it runs whenever jindo is built. The other jindo tests need real
796-
# OSS access and stay disabled.
797-
set(FS_TEST_JINDO_SOURCES)
794+
set(PAIMON_JINDO_FS_TEST_SOURCES)
795+
set(PAIMON_JINDO_FS_TEST_LINK_LIBS)
798796
if(PAIMON_ENABLE_JINDO)
799-
list(APPEND FS_TEST_JINDO_SOURCES fs/jindo/jindo_utils_test.cpp)
797+
# These unit tests do not access remote OSS.
798+
list(APPEND PAIMON_JINDO_FS_TEST_SOURCES fs/jindo/jindo_file_system_unit_test.cpp
799+
fs/jindo/jindo_utils_test.cpp)
800+
if(PAIMON_ENABLE_NETWORK_TESTS)
801+
# Factory and integration tests access real OSS.
802+
list(APPEND PAIMON_JINDO_FS_TEST_SOURCES
803+
fs/jindo/jindo_file_system_factory_test.cpp
804+
fs/jindo/jindo_file_system_test.cpp)
805+
endif()
806+
list(APPEND PAIMON_JINDO_FS_TEST_LINK_LIBS
807+
${PAIMON_JINDO_FILE_SYSTEM_STATIC_LINK_LIBS})
800808
endif()
801809

802810
add_paimon_test(fs_test
803811
SOURCES
804812
common/fs/file_system_test.cpp
805813
common/fs/resolving_file_system_test.cpp
806814
fs/local/local_file_test.cpp
807-
${FS_TEST_JINDO_SOURCES}
808-
# fs/jindo/jindo_file_system_factory_test.cpp
809-
# fs/jindo/jindo_file_system_test.cpp
815+
${PAIMON_JINDO_FS_TEST_SOURCES}
810816
STATIC_LINK_LIBS
811817
paimon_shared
812818
${PAIMON_LOCAL_FILE_SYSTEM_STATIC_LINK_LIBS}
813-
${PAIMON_JINDO_FILE_SYSTEM_STATIC_LINK_LIBS}
819+
${PAIMON_JINDO_FS_TEST_LINK_LIBS}
814820
test_utils_static
815821
${GTEST_LINK_TOOLCHAIN}
816822
EXTRA_INCLUDES
817823
${JINDOSDK_INCLUDE_DIR})
818824

825+
if(PAIMON_ENABLE_NETWORK_TESTS)
826+
target_compile_definitions(paimon-fs-test PRIVATE PAIMON_ENABLE_NETWORK_TESTS)
827+
endif()
828+
819829
endif()

src/paimon/common/fs/file_system_test.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1474,7 +1474,9 @@ TEST_P(FileSystemTest, TestAtomicStoreAlreadyExist) {
14741474
std::vector<std::string> GetTestValuesForFileSystemTest() {
14751475
std::vector<std::string> values;
14761476
values.emplace_back("local");
1477-
// values.emplace_back("jindo");
1477+
#if defined(PAIMON_ENABLE_NETWORK_TESTS) && defined(PAIMON_ENABLE_JINDO)
1478+
values.emplace_back("jindo");
1479+
#endif
14781480
return values;
14791481
}
14801482

src/paimon/core/operation/key_value_file_store_write_test.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,6 @@ TEST_F(KeyValueFileStoreWriteTest, TestSharedShreddingMapRestoreInitializesNextW
320320
{"fields.tags.map.shared-shredding.column-placement-policy", "plain"},
321321
{"write-only", "true"},
322322
{"bucket", "1"},
323-
{"enable-pk-commit-in-inte-test", ""},
324323
};
325324
auto logical_schema = arrow::schema({
326325
arrow::field("id", arrow::int32(), /*nullable=*/false),

src/paimon/fs/jindo/jindo_file_system.cpp

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
#include "paimon/fs/jindo/jindo_file_system.h"
1818

19+
#include <atomic>
1920
#include <cassert>
21+
#include <string_view>
2022
#include <utility>
2123

2224
#include "JdoFileInfo.hpp" // NOLINT(build/include_subdir)
@@ -26,6 +28,7 @@
2628
#include "fmt/format.h"
2729
#include "jdo_error.h" // NOLINT(build/include_subdir)
2830
#include "paimon/common/utils/math.h"
31+
#include "paimon/common/utils/path_util.h"
2932
#include "paimon/fs/jindo/jindo_file_status.h"
3033
#include "paimon/fs/jindo/jindo_utils.h"
3134

@@ -52,6 +55,29 @@ class JindoFileSystemImpl {
5255
std::unique_ptr<JdoFileSystem> fs_;
5356
};
5457

58+
namespace {
59+
60+
class AsyncReadState {
61+
public:
62+
explicit AsyncReadState(std::function<void(Status)>&& callback)
63+
: callback_(std::move(callback)) {}
64+
65+
void Complete(JdoStatus status) {
66+
if (completed_.exchange(true)) {
67+
return;
68+
}
69+
callback_(status.ok() ? Status::OK() : Status::IOError(status.errMsg()));
70+
}
71+
72+
std::string_view result;
73+
74+
private:
75+
std::atomic<bool> completed_{false};
76+
std::function<void(Status)> callback_;
77+
};
78+
79+
} // namespace
80+
5581
JindoFileSystem::JindoFileSystem(std::unique_ptr<JdoFileSystem>&& fs)
5682
: impl_(std::make_shared<JindoFileSystemImpl>(std::move(fs))) {}
5783

@@ -68,6 +94,18 @@ Result<std::unique_ptr<OutputStream>> JindoFileSystem::Create(const std::string&
6894
return Status::Invalid(
6995
fmt::format("do not allow overwrite, but the file {} already exists", path));
7096
}
97+
const std::string parent_path = PathUtil::GetParentDirPath(path);
98+
if (!parent_path.empty()) {
99+
PAIMON_ASSIGN_OR_RAISE(Path parent, PathUtil::ToPath(parent_path));
100+
// Do not issue mkdir for scheme-only or authority-only URI parents.
101+
if (!parent.path.empty()) {
102+
PAIMON_RETURN_NOT_OK(Mkdirs(parent_path));
103+
}
104+
}
105+
return OpenWriter(path);
106+
}
107+
108+
Result<std::unique_ptr<OutputStream>> JindoFileSystem::OpenWriter(const std::string& path) const {
71109
std::unique_ptr<JdoWriter> writer;
72110
PAIMON_RETURN_NOT_OK_FROM_JINDO(impl_->GetFileSystem()->openWriter(path, &writer));
73111
return std::make_unique<JindoOutputStream>(impl_, std::move(writer));
@@ -215,15 +253,17 @@ Result<int64_t> JindoInputStream::Length() const {
215253

216254
Result<int64_t> JindoInputStream::Read(char* buffer, int64_t size) {
217255
PAIMON_RETURN_NOT_OK(ValidateValueNonNegative(size, "read length"));
218-
PAIMON_RETURN_NOT_OK_FROM_JINDO(reader_->read(size, &result_, buffer));
219-
return result_.length();
256+
std::string_view result;
257+
PAIMON_RETURN_NOT_OK_FROM_JINDO(reader_->read(size, &result, buffer));
258+
return result.length();
220259
}
221260

222261
Result<int64_t> JindoInputStream::Read(char* buffer, int64_t size, int64_t offset) {
223262
PAIMON_RETURN_NOT_OK(ValidateValueNonNegative(size, "read length"));
224263
PAIMON_RETURN_NOT_OK(ValidateValueNonNegative(offset, "read offset"));
225-
PAIMON_RETURN_NOT_OK_FROM_JINDO(reader_->pread(offset, size, &result_, buffer));
226-
return result_.length();
264+
std::string_view result;
265+
PAIMON_RETURN_NOT_OK_FROM_JINDO(reader_->pread(offset, size, &result, buffer));
266+
return result.length();
227267
}
228268

229269
void JindoInputStream::ReadAsync(char* buffer, int64_t size, int64_t offset,
@@ -238,12 +278,17 @@ void JindoInputStream::ReadAsync(char* buffer, int64_t size, int64_t offset,
238278
callback(validate_status);
239279
return;
240280
}
241-
auto outer_callback = [=](JdoStatus status) {
242-
callback(status.ok() ? Status::OK() : Status::IOError(status.errMsg()));
243-
};
244-
auto task = reader_->preadAsync(offset, size, &result_, buffer, outer_callback);
281+
std::shared_ptr<AsyncReadState> state = std::make_shared<AsyncReadState>(std::move(callback));
282+
auto task = reader_->preadAsync(offset, size, &state->result, buffer,
283+
[state](JdoStatus status) { state->Complete(status); });
245284
assert(task);
246-
[[maybe_unused]] auto perform_status = task->perform();
285+
286+
auto perform_status = task->perform();
287+
if (!perform_status.ok()) {
288+
state->Complete(perform_status);
289+
[[maybe_unused]] auto status = task->cancel();
290+
return;
291+
}
247292
}
248293

249294
Status JindoInputStream::Close() {

src/paimon/fs/jindo/jindo_file_system.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
#include <functional>
2121
#include <memory>
2222
#include <string>
23-
#include <string_view>
2423
#include <vector>
2524

2625
#include "JdoFileSystem.hpp" // NOLINT(build/include_subdir)
@@ -57,6 +56,9 @@ class JindoFileSystem : public FileSystem {
5756

5857
Result<bool> Exists(const std::string& path) const override;
5958

59+
protected:
60+
virtual Result<std::unique_ptr<OutputStream>> OpenWriter(const std::string& path) const;
61+
6062
private:
6163
std::shared_ptr<JindoFileSystemImpl> impl_;
6264
};
@@ -80,7 +82,6 @@ class JindoInputStream : public InputStream {
8082
// the Jindo Reader.
8183
std::shared_ptr<JindoFileSystemImpl> fs_;
8284
std::unique_ptr<JdoReader> reader_;
83-
std::string_view result_;
8485
};
8586

8687
class JindoOutputStream : public OutputStream {

src/paimon/fs/jindo/jindo_file_system_test.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,19 @@
1414
* limitations under the License.
1515
*/
1616

17+
#include <chrono>
18+
#include <cstdint>
19+
#include <future>
20+
#include <string>
21+
#include <unordered_set>
22+
#include <vector>
23+
1724
#include "gtest/gtest.h"
1825
#include "paimon/fs/jindo/jindo_file_system_factory.h"
1926
#include "paimon/testing/utils/testharness.h"
27+
2028
namespace paimon::jindo::test {
29+
2130
// This test shows inconsistent behavior with the local file system in some abnormal scenarios.
2231
class JindoFileSystemTest : public ::testing::Test {
2332
public:
@@ -124,4 +133,73 @@ TEST_F(JindoFileSystemTest, TestSeek) {
124133
ASSERT_OK(in_stream->Close());
125134
}
126135

136+
TEST(JindoFileSystemPaginationTest, TestListDirAcrossOssPageBoundary) {
137+
constexpr int32_t kFileCount = 1234;
138+
const std::string test_dir = "oss://paimon-unittest/test_data/jindo_listdir_truncated_1234/";
139+
std::map<std::string, std::string> options = paimon::test::GetJindoTestOptions();
140+
141+
auto fs_factory = std::make_shared<JindoFileSystemFactory>();
142+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<FileSystem> fs, fs_factory->Create(test_dir, options));
143+
144+
std::vector<std::unique_ptr<BasicFileStatus>> file_statuses;
145+
ASSERT_OK(fs->ListDir(test_dir, &file_statuses));
146+
ASSERT_EQ(file_statuses.size(), kFileCount);
147+
148+
std::unordered_set<std::string> actual_paths;
149+
for (const std::unique_ptr<BasicFileStatus>& file_status : file_statuses) {
150+
ASSERT_TRUE(actual_paths.insert(file_status->GetPath()).second)
151+
<< "duplicate path: " << file_status->GetPath();
152+
}
153+
for (int32_t i = 0; i < kFileCount; ++i) {
154+
std::string index = std::to_string(i);
155+
index.insert(/*pos=*/0, /*count=*/4 - index.size(), /*ch=*/'0');
156+
ASSERT_NE(actual_paths.find(test_dir + "file-" + index + ".txt"), actual_paths.end());
157+
}
158+
}
159+
160+
TEST(JindoFileSystemAsyncReadTest, TestConcurrentReadAsyncAndReadFromOss) {
161+
constexpr int32_t kConcurrentReads = 64;
162+
constexpr int64_t kAsyncReadSize = 7;
163+
const std::string file_path = "oss://paimon-unittest/test_data/jindo_read_async_128mb.bin";
164+
std::map<std::string, std::string> options = paimon::test::GetJindoTestOptions();
165+
auto fs_factory = std::make_shared<JindoFileSystemFactory>();
166+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<FileSystem> fs, fs_factory->Create(file_path, options));
167+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<InputStream> input_stream, fs->Open(file_path));
168+
169+
std::vector<std::vector<char>> async_buffers(kConcurrentReads,
170+
std::vector<char>(kAsyncReadSize));
171+
std::vector<std::promise<Status>> promises(kConcurrentReads);
172+
std::vector<std::future<Status>> futures;
173+
futures.reserve(kConcurrentReads);
174+
for (std::promise<Status>& promise : promises) {
175+
futures.push_back(promise.get_future());
176+
}
177+
178+
std::vector<Status> sync_read_statuses;
179+
std::vector<int64_t> sync_read_sizes;
180+
sync_read_statuses.reserve(kConcurrentReads);
181+
sync_read_sizes.reserve(kConcurrentReads);
182+
for (int32_t i = 0; i < kConcurrentReads; ++i) {
183+
input_stream->ReadAsync(
184+
async_buffers[i].data(), async_buffers[i].size(), /*offset=*/0,
185+
[&promises, i](Status status) { promises[i].set_value(std::move(status)); });
186+
187+
char sync_buffer = 0;
188+
Result<int64_t> sync_read_result =
189+
input_stream->Read(&sync_buffer, /*size=*/1, /*offset=*/i);
190+
sync_read_statuses.push_back(sync_read_result.status());
191+
sync_read_sizes.push_back(sync_read_result.ok() ? sync_read_result.value() : -1);
192+
}
193+
194+
for (int32_t i = 0; i < kConcurrentReads; ++i) {
195+
ASSERT_EQ(futures[i].wait_for(std::chrono::seconds(60)), std::future_status::ready)
196+
<< "async read=" << i;
197+
ASSERT_OK(futures[i].get());
198+
}
199+
for (int32_t i = 0; i < kConcurrentReads; ++i) {
200+
ASSERT_OK(sync_read_statuses[i]) << "sync read=" << i;
201+
ASSERT_EQ(sync_read_sizes[i], 1) << "sync read=" << i;
202+
}
203+
}
204+
127205
} // namespace paimon::jindo::test

0 commit comments

Comments
 (0)