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
6 changes: 5 additions & 1 deletion sdk_v2/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,15 @@ if(WIN32)
list(APPEND FOUNDRY_LOCAL_PLATFORM_SOURCES
src/util/stacktrace_windows.cc
src/platform/windows/path.cc
src/platform/windows/file_io.cc
src/platform/windows/cross_process_file_lock.cc
)
else()
list(APPEND FOUNDRY_LOCAL_PLATFORM_SOURCES
src/util/stacktrace_posix.cc
src/platform/posix/path.cc
src/platform/posix/file_io.cc
src/platform/posix/cross_process_file_lock.cc
)
endif()

Expand Down Expand Up @@ -151,7 +155,7 @@ set(FOUNDRY_LOCAL_SOURCES
src/configuration.cc
src/download/blob_download_state.cc
src/download/blob_downloader.cc
src/download/cross_process_file_lock.cc
src/platform/cross_process_file_lock.cc
src/download/download_manager.cc
src/download/file_writer.cc
src/download/inference_model_writer.cc
Expand Down
4 changes: 2 additions & 2 deletions sdk_v2/cpp/docs/ResumableDownloadsPlan.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ shippable state. **No public C ABI changes** in any increment.

**Files added**

- `sdk_v2/cpp/src/download/cross_process_file_lock.h`
- `sdk_v2/cpp/src/download/cross_process_file_lock.cc`
- `sdk_v2/cpp/src/platform/cross_process_file_lock.h`
- `sdk_v2/cpp/src/platform/cross_process_file_lock.cc`

**Files modified**

Expand Down
2 changes: 1 addition & 1 deletion sdk_v2/cpp/src/download/download_manager.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "download/download_manager.h"
#include "download/cross_process_file_lock.h"
#include "platform/cross_process_file_lock.h"
#include "download/inference_model_writer.h"
#include "exception.h"
#include "log_level.h"
Expand Down
116 changes: 13 additions & 103 deletions sdk_v2/cpp/src/download/file_writer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,14 @@
#include "download/file_writer.h"
#include "exception.h"
#include "logger.h"
#include "platform/file_io.h"

#include <foundry_local/foundry_local_c.h>

#include <fstream>
#include <string>
#include <system_error>

#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <cerrno>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#endif

namespace fl {

namespace fs = std::filesystem;
Expand Down Expand Up @@ -69,112 +58,33 @@ void EnsureFileExistsAtSize(const fs::path& path, int64_t expected_size) {

FileWriter::FileWriter(ILogger& logger) : logger_(logger) {}

#ifdef _WIN32

FileWriter::~FileWriter() { Close(); }

void FileWriter::Open(const fs::path& path, int64_t expected_size) {
EnsureFileExistsAtSize(path, expected_size);
// FILE_SHARE_READ | FILE_SHARE_WRITE so the lock file / other tools can peek
// at the partial file without us erroring; positional WriteFile is safe
// regardless of share mode.
HANDLE h = ::CreateFileW(path.wstring().c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr);
if (h == INVALID_HANDLE_VALUE) {
std::string error;
handle_ = platform::OpenWritableFile(path, error);
if (handle_ == platform::kInvalidFileHandle) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INTERNAL,
"FileWriter open failed for " + path.string() + " (Win32 err " +
std::to_string(::GetLastError()) + ")");
"FileWriter open failed for " + path.string() + " (" + error + ")");
}
handle_ = h;
}

void FileWriter::WriteAt(int64_t offset, const uint8_t* data, size_t len) {
// Concurrent WriteFile calls with distinct OVERLAPPED offsets on the same
// handle are safe for non-overlapping ranges; the kernel orders them.
while (len > 0) {
OVERLAPPED ov{};
// Split the 64-bit file offset across the OVERLAPPED halves: the DWORD casts
// keep the low 32 bits in Offset and the high 32 bits in OffsetHigh.
ov.Offset = static_cast<DWORD>(static_cast<uint64_t>(offset));
ov.OffsetHigh = static_cast<DWORD>(static_cast<uint64_t>(offset) >> 32);
DWORD to_write = static_cast<DWORD>(len > 0x7FFFFFFFu ? 0x7FFFFFFFu : len);
DWORD written = 0;
if (!::WriteFile(handle_, data, to_write, &written, &ov)) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INTERNAL,
"FileWriter write failed at offset " + std::to_string(offset) + " (Win32 err " +
std::to_string(::GetLastError()) + ")");
}
if (written == 0) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INTERNAL,
"FileWriter short write at offset " + std::to_string(offset));
}
offset += static_cast<int64_t>(written);
data += written;
len -= written;
std::string error;
if (!platform::WriteFileAt(handle_, offset, data, len, error)) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INTERNAL, "FileWriter " + error);
}
}

void FileWriter::Close() {
if (handle_ != nullptr) {
if (!::CloseHandle(handle_)) {
const DWORD err = ::GetLastError();
logger_.Log(LogLevel::Warning,
"FileWriter: CloseHandle failed (Win32 err " + std::to_string(err) + ")");
if (handle_ != platform::kInvalidFileHandle) {
std::string error;
if (!platform::CloseFile(handle_, error)) {
logger_.Log(LogLevel::Warning, "FileWriter: " + error);
}
handle_ = nullptr;
}
}

#else // POSIX

FileWriter::~FileWriter() { Close(); }

void FileWriter::Open(const fs::path& path, int64_t expected_size) {
EnsureFileExistsAtSize(path, expected_size);
fd_ = ::open(path.c_str(), O_RDWR | O_CLOEXEC);
if (fd_ < 0) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INTERNAL,
"FileWriter open failed for " + path.string() + " (errno " +
std::to_string(errno) + ")");
handle_ = platform::kInvalidFileHandle;
}
}

void FileWriter::WriteAt(int64_t offset, const uint8_t* data, size_t len) {
while (len > 0) {
ssize_t n = ::pwrite(fd_, data, len, static_cast<off_t>(offset));
if (n < 0) {
if (errno == EINTR) continue;
FL_THROW(FOUNDRY_LOCAL_ERROR_INTERNAL,
"FileWriter pwrite failed at offset " + std::to_string(offset) + " (errno " +
std::to_string(errno) + ")");
}
if (n == 0) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INTERNAL,
"FileWriter short pwrite at offset " + std::to_string(offset));
}
offset += n;
data += n;
len -= static_cast<size_t>(n);
}
}

void FileWriter::Close() {
if (fd_ >= 0) {
// A failing close() can surface a deferred write error (e.g. EIO, or ENOSPC
// on delayed allocation / a networked filesystem), so the file may be
// incomplete even though every pwrite returned success. Log it for
// diagnosis. Don't retry: on Linux the descriptor is freed even when close()
// returns EINTR, so a retry could close an unrelated, since-reused fd.
if (::close(fd_) != 0) {
const int err = errno;
logger_.Log(LogLevel::Warning,
"FileWriter: close failed (errno " + std::to_string(err) + ")");
}
fd_ = -1;
}
}

#endif

} // namespace fl
12 changes: 5 additions & 7 deletions sdk_v2/cpp/src/download/file_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class ILogger;
/// Workers in a single download claim disjoint chunks, so concurrent `WriteAt`
/// calls always target non-overlapping byte ranges. Backed by `pwrite` (POSIX)
/// or `WriteFile` + `OVERLAPPED` (Windows): the OS arbitrates concurrent writes
/// to disjoint ranges, so no user-space lock is taken.
/// to disjoint ranges, so no user-space lock is taken. The OS-specific calls
/// live in `src/platform/file_io.*`.
class FileWriter {
public:
explicit FileWriter(ILogger& logger);
Expand All @@ -38,12 +39,9 @@ class FileWriter {

private:
ILogger& logger_;
#ifdef _WIN32
// Win32 HANDLE. Holds a valid handle while open, nullptr otherwise.
void* handle_ = nullptr;
#else
int fd_ = -1;
#endif
// Native file handle (Win32 HANDLE or POSIX fd) as an integer; see
// src/platform/file_io.h. kInvalidFileHandle (-1) when not open.
std::intptr_t handle_ = -1;
};

} // namespace fl
48 changes: 48 additions & 0 deletions sdk_v2/cpp/src/platform/cross_process_file_lock.cc
Comment thread
bmehta001 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Cross-platform orchestration for CrossProcessFileLock. The platform-specific
// pieces — the lock handle (State) and its releasing destructor,
// FormatProcessInfo, the CrossProcessFileLock destructor/constructor, and
// TryAcquireForDirectory — live in
// src/platform/{windows,posix}/cross_process_file_lock.cc.
#include "platform/cross_process_file_lock.h"
#include "exception.h"

#include <foundry_local/foundry_local_c.h>

#include <chrono>
#include <thread>

namespace fl {

std::unique_ptr<CrossProcessFileLock> CrossProcessFileLock::WaitForDirectoryLock(
const std::filesystem::path& directory,
const CancellationPredicate& is_cancelled,
ILogger& logger,
std::chrono::milliseconds poll_interval,
std::chrono::milliseconds timeout) {
auto deadline = std::chrono::steady_clock::now() + timeout;
// `is_cancelled` is the caller's progress callback, which also serves as the
// liveness heartbeat — it emits 0% on every invocation. We therefore poll it
// on a single cadence (once per `poll_interval`) rather than on a separate
// fast cancellation tick: a faster tick would spam the user callback (~10x/s)
// for the entire wait, and cancelling a multi-minute cross-process wait a
// second sooner is imperceptible. There is no separate cancellation channel
// to decouple the heartbeat from.
while (true) {
if (is_cancelled && is_cancelled()) {
FL_THROW(FOUNDRY_LOCAL_ERROR_OPERATION_CANCELLED, "lock acquisition cancelled");
}
auto lock = CrossProcessFileLock::TryAcquireForDirectory(directory, logger);
if (lock) {
return lock;
}
if (std::chrono::steady_clock::now() >= deadline) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INTERNAL,
"timed out waiting for cross-process download lock on '" + directory.string() + "'");
}
std::this_thread::sleep_for(poll_interval);
}
}

} // namespace fl
34 changes: 34 additions & 0 deletions sdk_v2/cpp/src/platform/file_io.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once

#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <string>

namespace fl::platform {

/// Native file handle (a Win32 HANDLE or a POSIX file descriptor) carried as an
/// integer so callers don't depend on platform headers. `kInvalidFileHandle`
/// matches both INVALID_HANDLE_VALUE ((HANDLE)-1) and a POSIX fd of -1.
inline constexpr std::intptr_t kInvalidFileHandle = -1;

/// Open an existing file for positional read/write. Returns a handle that is not
/// `kInvalidFileHandle` on success; on failure returns `kInvalidFileHandle` and
/// sets `error` to a human-readable OS error (e.g. "Win32 err 5" / "errno 13").
std::intptr_t OpenWritableFile(const std::filesystem::path& path, std::string& error);

/// Write all `len` bytes from `data` at byte offset `offset`. Safe for concurrent
/// calls on the same handle targeting disjoint ranges. Returns true on success;
/// on failure returns false and sets `error` (e.g. "pwrite failed at offset N
/// (errno M)").
bool WriteFileAt(std::intptr_t handle, std::int64_t offset, const std::uint8_t* data, std::size_t len,
std::string& error);

/// Close `handle`. Returns true on success; on failure returns false and sets
/// `error`. A failing close can surface a deferred write error, so the data may
/// be incomplete even when every write succeeded.
bool CloseFile(std::intptr_t handle, std::string& error);

} // namespace fl::platform
Loading