Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@

#include <lucene++/LuceneHeaders.h>
#include <lucene++/QueryParser.h>
#include <lucene++/BooleanQuery.h>

Check warning on line 15 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <lucene++/BooleanQuery.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <lucene++/QueryWrapperFilter.h>

Check warning on line 16 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <lucene++/QueryWrapperFilter.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <lucene++/WildcardQuery.h>

Check warning on line 17 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <lucene++/WildcardQuery.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <lucene++/NativeFSLockFactory.h>

Check warning on line 18 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <lucene++/NativeFSLockFactory.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <dfm-search/field_names.h>

Check warning on line 20 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dfm-search/field_names.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <dfm-search/timerangefilter.h>

Check warning on line 21 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dfm-search/timerangefilter.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include "3rdparty/fulltext/chineseanalyzer.h"

Check warning on line 23 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "3rdparty/fulltext/chineseanalyzer.h" not found.
#include "utils/cancellablecollector.h"

Check warning on line 24 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "utils/cancellablecollector.h" not found.
#include "utils/contenthighlighter.h"

Check warning on line 25 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "utils/contenthighlighter.h" not found.
#include "utils/lucenecommitlockguard.h"

Check warning on line 26 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "utils/lucenecommitlockguard.h" not found.
#include "utils/lucenequeryutils.h"
#include "utils/searchutility.h"
#include "utils/lucene_cancellation_compat.h"
Expand Down Expand Up @@ -419,16 +421,29 @@
SearchCancellationGuard guard(cancelledFlag);

try {
// 获取索引目录
FSDirectoryPtr directory = FSDirectory::open(m_indexDir.toStdWString());
// 获取索引目录;显式传入 NativeFSLockFactory,
// 让本进程的 commit.lock 与写端(另一个进程的 IndexWriter)
// 共用同一把 fcntl 锁,从而同步 writer 的 commit 窗口。
// 缓存 FSDirectory 避免每次搜索都重建对象。
{
QMutexLocker locker(&m_readerMutex);
if (!m_cachedDirectory) {
m_cachedDirectory = FSDirectory::open(
m_indexDir.toStdWString(),
newLucene<NativeFSLockFactory>(m_indexDir.toStdWString()));
}
}
FSDirectoryPtr directory = m_cachedDirectory;
if (!directory) {
qWarning() << "Failed to open index directory:" << m_indexDir;
emit errorOccurred(SearchError(ContentSearchErrorCode::ContentIndexNotFound));
return;
}

// 获取索引读取器
IndexReaderPtr reader = IndexReader::open(directory, true);
// 获取索引读取器(带 commit.lock 同步 + 版本缓存)。
// IndexReader::open() 必须运行在 commit.lock 持有期间,
// 否则可能读到 writer commit() 中间状态而触发段解析错误。
IndexReaderPtr reader = getOrCreateReader(directory);
if (!reader || reader->numDocs() == 0) {
qWarning() << "Index is empty or cannot be opened";
emit errorOccurred(SearchError(ContentSearchErrorCode::ContentIndexNotFound));
Expand Down Expand Up @@ -509,4 +524,84 @@
m_cancelledRef->store(true);
}

Lucene::IndexReaderPtr ContentIndexedStrategy::getOrCreateReader(const Lucene::FSDirectoryPtr &directory)
{
// Fast path: cached reader 仍反映最新 commit。
// 注意:fast path 不持 commit.lock,因为 IndexReader 在 open() 期间
// 已经固定了 segments 引用的所有段文件 fd;后续 writer commit() 只会
// 创建新 segments_N + 新段文件,老 reader 持有的 fd 不会被 writer 触碰,
// 所以 read-only 操作与并发 commit 安全共存。
// isCurrent() 内部仅读 segments_N 的版本号,不读取段数据,代价极低。
{
QMutexLocker locker(&m_readerMutex);
if (m_cachedReader) {
try {
if (m_cachedReader->isCurrent()) {
return m_cachedReader;
}
} catch (const Lucene::LuceneException &e) {
qWarning() << "IndexReader::isCurrent() failed:" << QString::fromStdWString(e.getError());
} catch (const std::exception &e) {
qWarning() << "IndexReader::isCurrent() std exception:" << e.what();
}
}
}
// Mutex released; concurrent searches can proceed.

// Slow path: acquire commit.lock before any open/reopen so we don't observe
// an in-flight commit(). open() reads segments_N and segment files; if a
// commit is in progress, the resulting reader can reference torn segment
// data and later corrupt the heap inside weight->scorer().
LuceneCommitLockGuard commitLock(directory, /*timeoutMs=*/1000, /*maxAttempts=*/3);
if (!commitLock.acquired()) {
qWarning() << "Cannot acquire commit.lock, abort search this round";
return nullptr;
}

// Double-check: another thread may have already reopened while we
// were waiting for commit.lock.
IndexReaderPtr cachedSnapshot;
{
QMutexLocker locker(&m_readerMutex);
cachedSnapshot = m_cachedReader;
if (cachedSnapshot) {
try {
if (cachedSnapshot->isCurrent()) {
return cachedSnapshot;
}
} catch (...) {
// isCurrent() failed; proceed with reopen below
}
}
}

IndexReaderPtr result;
try {
if (cachedSnapshot) {
result = cachedSnapshot->reopen(true);
if (!result) {
result = IndexReader::open(directory, true);
}
} else {
result = IndexReader::open(directory, true);
}
} catch (const Lucene::LuceneException &e) {
qWarning() << "IndexReader open/reopen failed:" << QString::fromStdWString(e.getError());
QMutexLocker locker(&m_readerMutex);
m_cachedReader.reset();
return nullptr;
} catch (const std::exception &e) {
qWarning() << "IndexReader open/reopen std exception:" << e.what();
QMutexLocker locker(&m_readerMutex);
m_cachedReader.reset();
return nullptr;
}

if (result) {
QMutexLocker locker(&m_readerMutex);
m_cachedReader = result;
}
return result;
}

DFM_SEARCH_END_NS
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

#include "basestrategy.h"

#include <QMutex>

#include <lucene++/LuceneHeaders.h>
#include <lucene++/FSDirectory.h>
#include <lucene++/IndexReader.h>
#include <lucene++/QueryParser.h>
#include <lucene++/BooleanQuery.h>
#include <lucene++/QueryWrapperFilter.h>
Expand Down Expand Up @@ -60,9 +64,15 @@ class ContentIndexedStrategy : public ContentBaseStrategy
void processSearchResults(const Lucene::IndexSearcherPtr &searcher,
const Lucene::Collection<Lucene::ScoreDocPtr> &scoreDocs);

// 获取/创建 IndexReader;带 commit.lock 同步 + 版本缓存
Lucene::IndexReaderPtr getOrCreateReader(const Lucene::FSDirectoryPtr &directory);

QString m_indexDir;
Lucene::QueryPtr m_currentQuery; // 存储当前查询
QStringList m_keywords;
QMutex m_readerMutex; // 保护 m_cachedReader 的并发访问
Lucene::IndexReaderPtr m_cachedReader; // 复用的 reader,由 m_readerMutex 守护
Lucene::FSDirectoryPtr m_cachedDirectory; // 缓存的 FSDirectory,避免每次搜索重建
};

DFM_SEARCH_END_NS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <lucene++/BooleanQuery.h>
#include <lucene++/QueryWrapperFilter.h>
#include <lucene++/WildcardQuery.h>
#include <lucene++/NativeFSLockFactory.h>

#include <dfm-search/field_names.h>
#include <dfm-search/timerangefilter.h>
Expand All @@ -23,6 +24,7 @@
#include "utils/contenthighlighter.h"
#include "utils/lucenequeryutils.h"
#include "utils/searchutility.h"
#include "utils/lucenecommitlockguard.h"
#include "utils/lucene_cancellation_compat.h"
#include "utils/timerangeutils.h"

Expand Down Expand Up @@ -418,16 +420,28 @@ void OcrTextIndexedStrategy::performOcrTextSearch(const SearchQuery &query)
SearchCancellationGuard guard(cancelledFlag);

try {
// Get index directory
FSDirectoryPtr directory = FSDirectory::open(m_indexDir.toStdWString());
// 获取索引目录;显式传入 NativeFSLockFactory,
// 让本进程的 commit.lock 与写端(另一个进程的 IndexWriter)
// 共用同一把 fcntl 锁,从而同步 writer 的 commit 窗口。
{
QMutexLocker locker(&m_readerMutex);
if (!m_cachedDirectory) {
m_cachedDirectory = FSDirectory::open(
m_indexDir.toStdWString(),
newLucene<NativeFSLockFactory>(m_indexDir.toStdWString()));
}
}
FSDirectoryPtr directory = m_cachedDirectory;
if (!directory) {
qWarning() << "Failed to open OCR text index directory:" << m_indexDir;
emit errorOccurred(SearchError(OcrTextSearchErrorCode::OcrTextIndexNotFound));
return;
}

// Get index reader
IndexReaderPtr reader = IndexReader::open(directory, true);
// 获取索引读取器(带 commit.lock 同步 + 版本缓存)。
// IndexReader::open() 必须运行在 commit.lock 持有期间,
// 否则可能读到 writer commit() 中间状态而触发段解析错误。
IndexReaderPtr reader = getOrCreateReader(directory);
if (!reader || reader->numDocs() == 0) {
qWarning() << "OCR text index is empty or cannot be opened";
emit errorOccurred(SearchError(OcrTextSearchErrorCode::OcrTextIndexNotFound));
Expand Down Expand Up @@ -506,4 +520,84 @@ void OcrTextIndexedStrategy::cancel()
m_cancelledRef->store(true);
}

Lucene::IndexReaderPtr OcrTextIndexedStrategy::getOrCreateReader(const Lucene::FSDirectoryPtr &directory)
{
// Fast path: cached reader 仍反映最新 commit。
// 注意:fast path 不持 commit.lock,因为 IndexReader 在 open() 期间
// 已经固定了 segments 引用的所有段文件 fd;后续 writer commit() 只会
// 创建新 segments_N + 新段文件,老 reader 持有的 fd 不会被 writer 触碰,
// 所以 read-only 操作与并发 commit 安全共存。
// isCurrent() 内部仅读 segments_N 的版本号,不读取段数据,代价极低。
{
QMutexLocker locker(&m_readerMutex);
if (m_cachedReader) {
try {
if (m_cachedReader->isCurrent()) {
return m_cachedReader;
}
} catch (const Lucene::LuceneException &e) {
qWarning() << "IndexReader::isCurrent() failed:" << QString::fromStdWString(e.getError());
} catch (const std::exception &e) {
qWarning() << "IndexReader::isCurrent() std exception:" << e.what();
}
}
}
// Mutex released; concurrent searches can proceed.

// Slow path: acquire commit.lock before any open/reopen so we don't observe
// an in-flight commit(). open() reads segments_N and segment files; if a
// commit is in progress, the resulting reader can reference torn segment
// data and later corrupt the heap inside weight->scorer().
LuceneCommitLockGuard commitLock(directory, /*timeoutMs=*/1000, /*maxAttempts=*/3);
if (!commitLock.acquired()) {
qWarning() << "Cannot acquire commit.lock, abort OCR text search this round";
return nullptr;
}

// Double-check: another thread may have already reopened while we
// were waiting for commit.lock.
IndexReaderPtr cachedSnapshot;
{
QMutexLocker locker(&m_readerMutex);
cachedSnapshot = m_cachedReader;
if (cachedSnapshot) {
try {
if (cachedSnapshot->isCurrent()) {
return cachedSnapshot;
}
} catch (...) {
// isCurrent() failed; proceed with reopen below
}
}
}

IndexReaderPtr result;
try {
if (cachedSnapshot) {
result = cachedSnapshot->reopen(true);
if (!result) {
result = IndexReader::open(directory, true);
}
} else {
result = IndexReader::open(directory, true);
}
} catch (const Lucene::LuceneException &e) {
qWarning() << "IndexReader open/reopen failed:" << QString::fromStdWString(e.getError());
QMutexLocker locker(&m_readerMutex);
m_cachedReader.reset();
return nullptr;
} catch (const std::exception &e) {
qWarning() << "IndexReader open/reopen std exception:" << e.what();
QMutexLocker locker(&m_readerMutex);
m_cachedReader.reset();
return nullptr;
}

if (result) {
QMutexLocker locker(&m_readerMutex);
m_cachedReader = result;
}
return result;
}

DFM_SEARCH_END_NS
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

#include "basestrategy.h"

#include <QMutex>

#include <lucene++/LuceneHeaders.h>
#include <lucene++/FSDirectory.h>
#include <lucene++/IndexReader.h>
#include <lucene++/QueryParser.h>
#include <lucene++/BooleanQuery.h>
#include <lucene++/QueryWrapperFilter.h>
Expand Down Expand Up @@ -62,9 +66,15 @@ class OcrTextIndexedStrategy : public OcrTextBaseStrategy
void processSearchResults(const Lucene::IndexSearcherPtr &searcher,
const Lucene::Collection<Lucene::ScoreDocPtr> &scoreDocs);

// 获取/创建 IndexReader;带 commit.lock 同步 + 版本缓存
Lucene::IndexReaderPtr getOrCreateReader(const Lucene::FSDirectoryPtr &directory);

QString m_indexDir;
Lucene::QueryPtr m_currentQuery;
QStringList m_keywords;
QMutex m_readerMutex; // 保护 m_cachedReader 的并发访问
Lucene::IndexReaderPtr m_cachedReader; // 复用的 reader,由 m_readerMutex 守护
Lucene::FSDirectoryPtr m_cachedDirectory; // 缓存的 FSDirectory,避免每次搜索重建
};

DFM_SEARCH_END_NS
Expand Down
57 changes: 57 additions & 0 deletions src/dfm-search/dfm-search-lib/utils/lucenecommitlockguard.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "lucenecommitlockguard.h"

#include <QDebug>

using namespace Lucene;

DFM_SEARCH_BEGIN_NS

LuceneCommitLockGuard::LuceneCommitLockGuard(const FSDirectoryPtr &dir, int timeoutMs, int maxAttempts)
: m_lock(dir ? dir->makeLock(L"commit.lock") : nullptr)
, m_acquired(false)
{
if (!m_lock) {
qWarning() << "LuceneCommitLockGuard: failed to create commit.lock handle";
return;
}

for (int attempt = 0; attempt < maxAttempts; ++attempt) {
try {
if (m_lock->obtain(timeoutMs)) {
m_acquired = true;
return;
}
} catch (const Lucene::LockObtainFailedException &e) {
qWarning() << "LuceneCommitLockGuard: commit.lock acquire timeout, attempt"
<< (attempt + 1) << "of" << maxAttempts
<< ":" << QString::fromStdWString(e.getError());
} catch (const Lucene::LuceneException &e) {
qWarning() << "LuceneCommitLockGuard: commit.lock acquire failed, attempt"
<< (attempt + 1) << "of" << maxAttempts
<< ":" << QString::fromStdWString(e.getError());
break;
}
}
}

LuceneCommitLockGuard::~LuceneCommitLockGuard()
{
if (m_acquired && m_lock) {
try {
m_lock->release();
} catch (const Lucene::LuceneException &e) {
qWarning() << "LuceneCommitLockGuard: failed to release commit.lock:"
<< QString::fromStdWString(e.getError());
} catch (const std::exception &e) {
qWarning() << "LuceneCommitLockGuard: failed to release commit.lock:" << e.what();
} catch (...) {
qWarning() << "LuceneCommitLockGuard: unknown exception while releasing commit.lock";
}
m_acquired = false;
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
}
}

DFM_SEARCH_END_NS
Loading
Loading