Fix data race.
+ Add thread-safety with mutexes in `PieceTable` and `Buffer` + Bump version to 1.5.9
This commit is contained in:
@@ -412,6 +412,7 @@ Buffer::GetLineView(std::size_t row) const
|
|||||||
void
|
void
|
||||||
Buffer::ensure_rows_cache() const
|
Buffer::ensure_rows_cache() const
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(buffer_mutex_);
|
||||||
if (!rows_cache_dirty_)
|
if (!rows_cache_dirty_)
|
||||||
return;
|
return;
|
||||||
rows_.clear();
|
rows_.clear();
|
||||||
|
|||||||
3
Buffer.h
3
Buffer.h
@@ -14,6 +14,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "syntax/HighlighterEngine.h"
|
#include "syntax/HighlighterEngine.h"
|
||||||
#include "Highlight.h"
|
#include "Highlight.h"
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
// Forward declaration for swap journal integration
|
// Forward declaration for swap journal integration
|
||||||
namespace kte {
|
namespace kte {
|
||||||
@@ -482,4 +483,6 @@ private:
|
|||||||
std::unique_ptr<kte::HighlighterEngine> highlighter_;
|
std::unique_ptr<kte::HighlighterEngine> highlighter_;
|
||||||
// Non-owning pointer to swap recorder managed by Editor/SwapManager
|
// Non-owning pointer to swap recorder managed by Editor/SwapManager
|
||||||
kte::SwapRecorder *swap_rec_ = nullptr;
|
kte::SwapRecorder *swap_rec_ = nullptr;
|
||||||
|
|
||||||
|
mutable std::mutex buffer_mutex_;
|
||||||
};
|
};
|
||||||
@@ -4,7 +4,7 @@ project(kte)
|
|||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(KTE_VERSION "1.5.8")
|
set(KTE_VERSION "1.5.9")
|
||||||
|
|
||||||
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
|
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
|
||||||
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
|
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ PieceTable::addPieceFront(Source src, std::size_t start, std::size_t len)
|
|||||||
void
|
void
|
||||||
PieceTable::materialize() const
|
PieceTable::materialize() const
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
if (!dirty_) {
|
if (!dirty_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -348,6 +349,7 @@ PieceTable::coalesceNeighbors(std::size_t index)
|
|||||||
void
|
void
|
||||||
PieceTable::InvalidateLineIndex() const
|
PieceTable::InvalidateLineIndex() const
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
line_index_dirty_ = true;
|
line_index_dirty_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,22 +357,29 @@ PieceTable::InvalidateLineIndex() const
|
|||||||
void
|
void
|
||||||
PieceTable::RebuildLineIndex() const
|
PieceTable::RebuildLineIndex() const
|
||||||
{
|
{
|
||||||
if (!line_index_dirty_)
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
if (!line_index_dirty_) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
line_index_.clear();
|
line_index_.clear();
|
||||||
line_index_.push_back(0);
|
line_index_.push_back(0);
|
||||||
|
|
||||||
std::size_t pos = 0;
|
std::size_t pos = 0;
|
||||||
for (const auto &pc: pieces_) {
|
for (const auto &pc: pieces_) {
|
||||||
const std::string &src = pc.src == Source::Original ? original_ : add_;
|
const std::string &src = pc.src == Source::Original ? original_ : add_;
|
||||||
const char *base = src.data() + static_cast<std::ptrdiff_t>(pc.start);
|
const char *base = src.data() + static_cast<std::ptrdiff_t>(pc.start);
|
||||||
|
|
||||||
for (std::size_t j = 0; j < pc.len; ++j) {
|
for (std::size_t j = 0; j < pc.len; ++j) {
|
||||||
if (base[j] == '\n') {
|
if (base[j] == '\n') {
|
||||||
// next line starts after the newline
|
// next line starts after the newline
|
||||||
line_index_.push_back(pos + j + 1);
|
line_index_.push_back(pos + j + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pos += pc.len;
|
pos += pc.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
line_index_dirty_ = false;
|
line_index_dirty_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,14 +701,18 @@ PieceTable::GetRange(std::size_t byte_offset, std::size_t len) const
|
|||||||
len = total_size_ - byte_offset;
|
len = total_size_ - byte_offset;
|
||||||
|
|
||||||
// Fast path: return cached value if version/offset/len match
|
// Fast path: return cached value if version/offset/len match
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
if (range_cache_.valid && range_cache_.version == version_ &&
|
if (range_cache_.valid && range_cache_.version == version_ &&
|
||||||
range_cache_.off == byte_offset && range_cache_.len == len) {
|
range_cache_.off == byte_offset && range_cache_.len == len) {
|
||||||
return range_cache_.data;
|
return range_cache_.data;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string out;
|
std::string out;
|
||||||
out.reserve(len);
|
out.reserve(len);
|
||||||
if (!dirty_) {
|
if (!dirty_) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
// Already materialized; slice directly
|
// Already materialized; slice directly
|
||||||
out.assign(materialized_.data() + static_cast<std::ptrdiff_t>(byte_offset), len);
|
out.assign(materialized_.data() + static_cast<std::ptrdiff_t>(byte_offset), len);
|
||||||
} else {
|
} else {
|
||||||
@@ -723,11 +736,14 @@ PieceTable::GetRange(std::size_t byte_offset, std::size_t len) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update cache
|
// Update cache
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
range_cache_.valid = true;
|
range_cache_.valid = true;
|
||||||
range_cache_.version = version_;
|
range_cache_.version = version_;
|
||||||
range_cache_.off = byte_offset;
|
range_cache_.off = byte_offset;
|
||||||
range_cache_.len = len;
|
range_cache_.len = len;
|
||||||
range_cache_.data = out;
|
range_cache_.data = out;
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,15 +755,21 @@ PieceTable::Find(const std::string &needle, std::size_t start) const
|
|||||||
return start <= total_size_ ? start : std::numeric_limits<std::size_t>::max();
|
return start <= total_size_ ? start : std::numeric_limits<std::size_t>::max();
|
||||||
if (start > total_size_)
|
if (start > total_size_)
|
||||||
return std::numeric_limits<std::size_t>::max();
|
return std::numeric_limits<std::size_t>::max();
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
if (find_cache_.valid &&
|
if (find_cache_.valid &&
|
||||||
find_cache_.version == version_ &&
|
find_cache_.version == version_ &&
|
||||||
find_cache_.needle == needle &&
|
find_cache_.needle == needle &&
|
||||||
find_cache_.start == start) {
|
find_cache_.start == start) {
|
||||||
return find_cache_.result;
|
return find_cache_.result;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
materialize();
|
materialize();
|
||||||
auto pos = materialized_.find(needle, start);
|
std::size_t pos;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
pos = materialized_.find(needle, start);
|
||||||
if (pos == std::string::npos)
|
if (pos == std::string::npos)
|
||||||
pos = std::numeric_limits<std::size_t>::max();
|
pos = std::numeric_limits<std::size_t>::max();
|
||||||
// Update cache
|
// Update cache
|
||||||
@@ -756,6 +778,7 @@ PieceTable::Find(const std::string &needle, std::size_t start) const
|
|||||||
find_cache_.needle = needle;
|
find_cache_.needle = needle;
|
||||||
find_cache_.start = start;
|
find_cache_.start = start;
|
||||||
find_cache_.result = pos;
|
find_cache_.result = pos;
|
||||||
|
}
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -764,6 +787,9 @@ void
|
|||||||
PieceTable::WriteToStream(std::ostream &out) const
|
PieceTable::WriteToStream(std::ostream &out) const
|
||||||
{
|
{
|
||||||
// Stream the content piece-by-piece without forcing full materialization
|
// Stream the content piece-by-piece without forcing full materialization
|
||||||
|
// No lock needed for original_ and add_ if they are not being modified.
|
||||||
|
// Since this is a const method and kte's piece table isn't modified by multiple threads
|
||||||
|
// (only queried), we just iterate pieces_.
|
||||||
for (const auto &p: pieces_) {
|
for (const auto &p: pieces_) {
|
||||||
if (p.len == 0)
|
if (p.len == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
|
||||||
class PieceTable {
|
class PieceTable {
|
||||||
@@ -181,4 +182,6 @@ private:
|
|||||||
|
|
||||||
mutable RangeCache range_cache_;
|
mutable RangeCache range_cache_;
|
||||||
mutable FindCache find_cache_;
|
mutable FindCache find_cache_;
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user