Skip to content

Commit a4bc79f

Browse files
committed
fix(jindo): make async reads concurrency-safe
1 parent 3b3d665 commit a4bc79f

10 files changed

Lines changed: 158 additions & 16 deletions

File tree

CMakeLists.txt

Lines changed: 10 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_JINDO_TESTS "Whether to enable Jindo tests that access remote OSS"
61+
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,14 @@ endif()
7072
if(PAIMON_ENABLE_JINDO)
7173
add_definitions(-DPAIMON_ENABLE_JINDO)
7274
endif()
75+
if(PAIMON_ENABLE_JINDO_TESTS)
76+
if(NOT PAIMON_BUILD_TESTS)
77+
message(FATAL_ERROR "PAIMON_ENABLE_JINDO_TESTS requires PAIMON_BUILD_TESTS=ON")
78+
endif()
79+
if(NOT PAIMON_ENABLE_JINDO)
80+
message(FATAL_ERROR "PAIMON_ENABLE_JINDO_TESTS requires PAIMON_ENABLE_JINDO=ON")
81+
endif()
82+
endif()
7383
if(PAIMON_USE_CXX11_ABI)
7484
add_definitions(-D_GLIBCXX_USE_CXX11_ABI=1)
7585
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_JINDO_TESTS "Enable Jindo tests that access remote OSS"
111+
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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ 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, buffer, and any externally referenced callback
102+
/// state alive until the callback is invoked.
101103
virtual void ReadAsync(char* buffer, int64_t size, int64_t offset,
102104
std::function<void(Status)>&& callback) = 0;
103105

src/paimon/CMakeLists.txt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -764,18 +764,31 @@ if(PAIMON_BUILD_TESTS)
764764
${TEST_STATIC_LINK_LIBS}
765765
${GTEST_LINK_TOOLCHAIN})
766766

767+
set(PAIMON_JINDO_FS_TEST_SOURCES)
768+
set(PAIMON_JINDO_FS_TEST_LINK_LIBS)
769+
if(PAIMON_ENABLE_JINDO_TESTS)
770+
list(APPEND PAIMON_JINDO_FS_TEST_SOURCES
771+
fs/jindo/jindo_file_system_factory_test.cpp
772+
fs/jindo/jindo_file_system_test.cpp)
773+
list(APPEND PAIMON_JINDO_FS_TEST_LINK_LIBS
774+
${PAIMON_JINDO_FILE_SYSTEM_STATIC_LINK_LIBS})
775+
endif()
776+
767777
add_paimon_test(fs_test
768778
SOURCES
769779
common/fs/file_system_test.cpp
770780
common/fs/resolving_file_system_test.cpp
771781
fs/local/local_file_test.cpp
772-
# fs/jindo/jindo_file_system_factory_test.cpp
773-
# fs/jindo/jindo_file_system_test.cpp
782+
${PAIMON_JINDO_FS_TEST_SOURCES}
774783
STATIC_LINK_LIBS
775784
paimon_shared
776785
${PAIMON_LOCAL_FILE_SYSTEM_STATIC_LINK_LIBS}
777-
# ${PAIMON_JINDO_FILE_SYSTEM_STATIC_LINK_LIBS}
786+
${PAIMON_JINDO_FS_TEST_LINK_LIBS}
778787
test_utils_static
779788
${GTEST_LINK_TOOLCHAIN})
780789

790+
if(PAIMON_ENABLE_JINDO_TESTS)
791+
target_compile_definitions(paimon-fs-test PRIVATE PAIMON_ENABLE_JINDO_TESTS)
792+
endif()
793+
781794
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+
#ifdef PAIMON_ENABLE_JINDO_TESTS
1478+
values.emplace_back("jindo");
1479+
#endif
14781480
return values;
14791481
}
14801482

src/paimon/fs/jindo/jindo_file_system.cpp

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

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

19+
#include <atomic>
1920
#include <cassert>
2021
#include <utility>
2122

@@ -52,6 +53,29 @@ class JindoFileSystemImpl {
5253
std::unique_ptr<JdoFileSystem> fs_;
5354
};
5455

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

@@ -215,15 +239,17 @@ Result<int64_t> JindoInputStream::Length() const {
215239

216240
Result<int64_t> JindoInputStream::Read(char* buffer, int64_t size) {
217241
PAIMON_RETURN_NOT_OK(ValidateValueNonNegative(size, "read length"));
218-
PAIMON_RETURN_NOT_OK_FROM_JINDO(reader_->read(size, &result_, buffer));
219-
return result_.length();
242+
std::string_view result;
243+
PAIMON_RETURN_NOT_OK_FROM_JINDO(reader_->read(size, &result, buffer));
244+
return result.length();
220245
}
221246

222247
Result<int64_t> JindoInputStream::Read(char* buffer, int64_t size, int64_t offset) {
223248
PAIMON_RETURN_NOT_OK(ValidateValueNonNegative(size, "read length"));
224249
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();
250+
std::string_view result;
251+
PAIMON_RETURN_NOT_OK_FROM_JINDO(reader_->pread(offset, size, &result, buffer));
252+
return result.length();
227253
}
228254

229255
void JindoInputStream::ReadAsync(char* buffer, int64_t size, int64_t offset,
@@ -238,12 +264,16 @@ void JindoInputStream::ReadAsync(char* buffer, int64_t size, int64_t offset,
238264
callback(validate_status);
239265
return;
240266
}
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);
267+
std::shared_ptr<AsyncReadState> state = std::make_shared<AsyncReadState>(std::move(callback));
268+
auto task = reader_->preadAsync(offset, size, &state->result, buffer,
269+
[state](JdoStatus status) { state->Complete(status); });
245270
assert(task);
246-
[[maybe_unused]] auto perform_status = task->perform();
271+
272+
auto perform_status = task->perform();
273+
if (!perform_status.ok()) {
274+
state->Complete(perform_status);
275+
return;
276+
}
247277
}
248278

249279
Status JindoInputStream::Close() {

src/paimon/fs/jindo/jindo_file_system.h

Lines changed: 0 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)
@@ -80,7 +79,6 @@ class JindoInputStream : public InputStream {
8079
// the Jindo Reader.
8180
std::shared_ptr<JindoFileSystemImpl> fs_;
8281
std::unique_ptr<JdoReader> reader_;
83-
std::string_view result_;
8482
};
8583

8684
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

test/inte/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ if(PAIMON_BUILD_TESTS)
4040
${TEST_STATIC_LINK_LIBS}
4141
test_utils_static
4242
${GTEST_LINK_TOOLCHAIN})
43+
if(PAIMON_ENABLE_JINDO_TESTS)
44+
target_compile_definitions(paimon-write-and-read-inte-test
45+
PRIVATE PAIMON_ENABLE_JINDO_TESTS)
46+
endif()
4347

4448
add_paimon_test(clean_inte_test
4549
STATIC_LINK_LIBS

test/inte/write_and_read_inte_test.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1096,7 +1096,9 @@ TEST_P(WriteAndReadInteTest, TestCharVarcharBinaryVarbinaryTypes) {
10961096

10971097
std::vector<std::pair<std::string, std::string>> GetTestValuesForWriteAndReadInteTest() {
10981098
std::vector<std::pair<std::string, std::string>> values = {{"parquet", "local"}};
1099-
// values.emplace_back("parquet", "jindo");
1099+
#ifdef PAIMON_ENABLE_JINDO_TESTS
1100+
values.emplace_back("parquet", "jindo");
1101+
#endif
11001102
#ifdef PAIMON_ENABLE_ORC
11011103
values.emplace_back("orc", "local");
11021104
#endif

0 commit comments

Comments
 (0)