mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2026-01-06 17:22:54 +00:00
Compare commits
9 Commits
remove-tru
...
race-condi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72bd0cfd4a | ||
|
|
1868fc1e2c | ||
|
|
451a0252ab | ||
|
|
42756c7474 | ||
|
|
598b076240 | ||
|
|
f1f96fc417 | ||
|
|
9c941603f5 | ||
|
|
fb3d6fa332 | ||
|
|
88fd7f091a |
@@ -36,15 +36,14 @@ notify = {version="4", optional=true}
|
|||||||
bit-set = "0.5"
|
bit-set = "0.5"
|
||||||
uuid = { version = "0.8", features = ["v4", "serde"] }
|
uuid = { version = "0.8", features = ["v4", "serde"] }
|
||||||
crossbeam = "0.7"
|
crossbeam = "0.7"
|
||||||
futures = "0.1"
|
futures = {version = "0.3", features=["thread-pool"] }
|
||||||
futures-cpupool = "0.1"
|
|
||||||
owning_ref = "0.4"
|
owning_ref = "0.4"
|
||||||
stable_deref_trait = "1.0.0"
|
stable_deref_trait = "1.0.0"
|
||||||
rust-stemmers = "1.1"
|
rust-stemmers = "1.1"
|
||||||
downcast-rs = { version="1.0" }
|
downcast-rs = { version="1.0" }
|
||||||
tantivy-query-grammar = { path="./query-grammar" }
|
tantivy-query-grammar = { path="./query-grammar" }
|
||||||
bitpacking = {version="0.8", default-features = false, features=["bitpacker4x"]}
|
bitpacking = {version="0.8", default-features = false, features=["bitpacker4x"]}
|
||||||
census = "0.2"
|
census = "0.3"
|
||||||
fnv = "1.0.6"
|
fnv = "1.0.6"
|
||||||
owned-read = "0.4"
|
owned-read = "0.4"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
|
|||||||
@@ -67,9 +67,7 @@ fn main() -> tantivy::Result<()> {
|
|||||||
|
|
||||||
// Now lets create a document and add our `PreTokenizedString` using
|
// Now lets create a document and add our `PreTokenizedString` using
|
||||||
// `add_pre_tokenized_text` method of `Document`
|
// `add_pre_tokenized_text` method of `Document`
|
||||||
let mut old_man_doc = Document::default();
|
let old_man_doc = doc!(title => title_tok, body => body_tok);
|
||||||
old_man_doc.add_pre_tokenized_text(title, &title_tok);
|
|
||||||
old_man_doc.add_pre_tokenized_text(body, &body_tok);
|
|
||||||
|
|
||||||
// ... now let's just add it to the IndexWriter
|
// ... now let's just add it to the IndexWriter
|
||||||
index_writer.add_document(old_man_doc);
|
index_writer.add_document(old_man_doc);
|
||||||
|
|||||||
@@ -104,23 +104,21 @@ impl Index {
|
|||||||
if Index::exists(&mmap_directory) {
|
if Index::exists(&mmap_directory) {
|
||||||
return Err(TantivyError::IndexAlreadyExists);
|
return Err(TantivyError::IndexAlreadyExists);
|
||||||
}
|
}
|
||||||
|
|
||||||
Index::create(mmap_directory, schema)
|
Index::create(mmap_directory, schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens or creates a new index in the provided directory
|
/// Opens or creates a new index in the provided directory
|
||||||
pub fn open_or_create<Dir: Directory>(dir: Dir, schema: Schema) -> Result<Index> {
|
pub fn open_or_create<Dir: Directory>(dir: Dir, schema: Schema) -> Result<Index> {
|
||||||
if Index::exists(&dir) {
|
if !Index::exists(&dir) {
|
||||||
let index = Index::open(dir)?;
|
return Index::create(dir, schema);
|
||||||
if index.schema() == schema {
|
}
|
||||||
Ok(index)
|
let index = Index::open(dir)?;
|
||||||
} else {
|
if index.schema() == schema {
|
||||||
Err(TantivyError::SchemaError(
|
Ok(index)
|
||||||
"An index exists but the schema does not match.".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Index::create(dir, schema)
|
Err(TantivyError::SchemaError(
|
||||||
|
"An index exists but the schema does not match.".to_string(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,12 +386,9 @@ mod tests {
|
|||||||
use crate::directory::RAMDirectory;
|
use crate::directory::RAMDirectory;
|
||||||
use crate::schema::Field;
|
use crate::schema::Field;
|
||||||
use crate::schema::{Schema, INDEXED, TEXT};
|
use crate::schema::{Schema, INDEXED, TEXT};
|
||||||
use crate::Index;
|
|
||||||
use crate::IndexReader;
|
use crate::IndexReader;
|
||||||
use crate::IndexWriter;
|
|
||||||
use crate::ReloadPolicy;
|
use crate::ReloadPolicy;
|
||||||
use std::thread;
|
use crate::{Directory, Index};
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_indexer_for_field() {
|
fn test_indexer_for_field() {
|
||||||
@@ -471,14 +466,14 @@ mod tests {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(reader.searcher().num_docs(), 0);
|
assert_eq!(reader.searcher().num_docs(), 0);
|
||||||
let mut writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
test_index_on_commit_reload_policy_aux(field, &index, &reader);
|
||||||
test_index_on_commit_reload_policy_aux(field, &mut writer, &reader);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mmap")]
|
#[cfg(feature = "mmap")]
|
||||||
mod mmap_specific {
|
mod mmap_specific {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::Directory;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
@@ -489,22 +484,20 @@ mod tests {
|
|||||||
let tempdir = TempDir::new().unwrap();
|
let tempdir = TempDir::new().unwrap();
|
||||||
let tempdir_path = PathBuf::from(tempdir.path());
|
let tempdir_path = PathBuf::from(tempdir.path());
|
||||||
let index = Index::create_in_dir(&tempdir_path, schema).unwrap();
|
let index = Index::create_in_dir(&tempdir_path, schema).unwrap();
|
||||||
let mut writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
|
||||||
writer.commit().unwrap();
|
|
||||||
let reader = index
|
let reader = index
|
||||||
.reader_builder()
|
.reader_builder()
|
||||||
.reload_policy(ReloadPolicy::OnCommit)
|
.reload_policy(ReloadPolicy::OnCommit)
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(reader.searcher().num_docs(), 0);
|
assert_eq!(reader.searcher().num_docs(), 0);
|
||||||
test_index_on_commit_reload_policy_aux(field, &mut writer, &reader);
|
test_index_on_commit_reload_policy_aux(field, &index, &reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_index_manual_policy_mmap() {
|
fn test_index_manual_policy_mmap() {
|
||||||
let schema = throw_away_schema();
|
let schema = throw_away_schema();
|
||||||
let field = schema.get_field("num_likes").unwrap();
|
let field = schema.get_field("num_likes").unwrap();
|
||||||
let index = Index::create_from_tempdir(schema).unwrap();
|
let mut index = Index::create_from_tempdir(schema).unwrap();
|
||||||
let mut writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
let mut writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
||||||
writer.commit().unwrap();
|
writer.commit().unwrap();
|
||||||
let reader = index
|
let reader = index
|
||||||
@@ -514,8 +507,12 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(reader.searcher().num_docs(), 0);
|
assert_eq!(reader.searcher().num_docs(), 0);
|
||||||
writer.add_document(doc!(field=>1u64));
|
writer.add_document(doc!(field=>1u64));
|
||||||
|
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||||
|
let _handle = index.directory_mut().watch(Box::new(move || {
|
||||||
|
let _ = sender.send(());
|
||||||
|
}));
|
||||||
writer.commit().unwrap();
|
writer.commit().unwrap();
|
||||||
thread::sleep(Duration::from_millis(500));
|
assert!(receiver.recv().is_ok());
|
||||||
assert_eq!(reader.searcher().num_docs(), 0);
|
assert_eq!(reader.searcher().num_docs(), 0);
|
||||||
reader.reload().unwrap();
|
reader.reload().unwrap();
|
||||||
assert_eq!(reader.searcher().num_docs(), 1);
|
assert_eq!(reader.searcher().num_docs(), 1);
|
||||||
@@ -535,39 +532,26 @@ mod tests {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(reader.searcher().num_docs(), 0);
|
assert_eq!(reader.searcher().num_docs(), 0);
|
||||||
let mut writer = write_index.writer_with_num_threads(1, 3_000_000).unwrap();
|
test_index_on_commit_reload_policy_aux(field, &write_index, &reader);
|
||||||
test_index_on_commit_reload_policy_aux(field, &mut writer, &reader);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_index_on_commit_reload_policy_aux(
|
fn test_index_on_commit_reload_policy_aux(field: Field, index: &Index, reader: &IndexReader) {
|
||||||
field: Field,
|
let mut reader_index = reader.index();
|
||||||
writer: &mut IndexWriter,
|
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||||
reader: &IndexReader,
|
let _watch_handle = reader_index.directory_mut().watch(Box::new(move || {
|
||||||
) {
|
let _ = sender.send(());
|
||||||
|
}));
|
||||||
|
let mut writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
||||||
assert_eq!(reader.searcher().num_docs(), 0);
|
assert_eq!(reader.searcher().num_docs(), 0);
|
||||||
writer.add_document(doc!(field=>1u64));
|
writer.add_document(doc!(field=>1u64));
|
||||||
writer.commit().unwrap();
|
writer.commit().unwrap();
|
||||||
let mut count = 0;
|
assert!(receiver.recv().is_ok());
|
||||||
for _ in 0..100 {
|
assert_eq!(reader.searcher().num_docs(), 1);
|
||||||
count = reader.searcher().num_docs();
|
|
||||||
if count > 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
thread::sleep(Duration::from_millis(100));
|
|
||||||
}
|
|
||||||
assert_eq!(count, 1);
|
|
||||||
writer.add_document(doc!(field=>2u64));
|
writer.add_document(doc!(field=>2u64));
|
||||||
writer.commit().unwrap();
|
writer.commit().unwrap();
|
||||||
let mut count = 0;
|
assert!(receiver.recv().is_ok());
|
||||||
for _ in 0..10 {
|
assert_eq!(reader.searcher().num_docs(), 2);
|
||||||
count = reader.searcher().num_docs();
|
|
||||||
if count > 1 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
thread::sleep(Duration::from_millis(100));
|
|
||||||
}
|
|
||||||
assert_eq!(count, 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test will not pass on windows, because windows
|
// This test will not pass on windows, because windows
|
||||||
@@ -584,9 +568,13 @@ mod tests {
|
|||||||
for i in 0u64..8_000u64 {
|
for i in 0u64..8_000u64 {
|
||||||
writer.add_document(doc!(field => i));
|
writer.add_document(doc!(field => i));
|
||||||
}
|
}
|
||||||
|
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||||
|
let _handle = directory.watch(Box::new(move || {
|
||||||
|
let _ = sender.send(());
|
||||||
|
}));
|
||||||
writer.commit().unwrap();
|
writer.commit().unwrap();
|
||||||
let mem_right_after_commit = directory.total_mem_usage();
|
let mem_right_after_commit = directory.total_mem_usage();
|
||||||
thread::sleep(Duration::from_millis(1_000));
|
assert!(receiver.recv().is_ok());
|
||||||
let reader = index
|
let reader = index
|
||||||
.reader_builder()
|
.reader_builder()
|
||||||
.reload_policy(ReloadPolicy::Manual)
|
.reload_policy(ReloadPolicy::Manual)
|
||||||
@@ -600,6 +588,11 @@ mod tests {
|
|||||||
reader.reload().unwrap();
|
reader.reload().unwrap();
|
||||||
let searcher = reader.searcher();
|
let searcher = reader.searcher();
|
||||||
assert_eq!(searcher.num_docs(), 8_000);
|
assert_eq!(searcher.num_docs(), 8_000);
|
||||||
assert!(mem_right_after_merge_finished < mem_right_after_commit);
|
assert!(
|
||||||
|
mem_right_after_merge_finished < mem_right_after_commit,
|
||||||
|
"(mem after merge){} is expected < (mem before merge){}",
|
||||||
|
mem_right_after_merge_finished,
|
||||||
|
mem_right_after_commit
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,6 +300,9 @@ mod tests {
|
|||||||
payload: None,
|
payload: None,
|
||||||
};
|
};
|
||||||
let json = serde_json::ser::to_string(&index_metas).expect("serialization failed");
|
let json = serde_json::ser::to_string(&index_metas).expect("serialization failed");
|
||||||
assert_eq!(json, r#"{"segments":[],"schema":[{"name":"text","type":"text","options":{"indexing":{"record":"position","tokenizer":"default"},"stored":false}}],"opstamp":0}"#);
|
assert_eq!(
|
||||||
|
json,
|
||||||
|
r#"{"segments":[],"schema":[{"name":"text","type":"text","options":{"indexing":{"record":"position","tokenizer":"default"},"stored":false}}],"opstamp":0}"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use std::io::Write;
|
|||||||
|
|
||||||
const COMMON_FOOTER_SIZE: usize = 4 * 5;
|
const COMMON_FOOTER_SIZE: usize = 4 * 5;
|
||||||
|
|
||||||
|
type CrcHashU32 = u32;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Footer {
|
pub struct Footer {
|
||||||
pub tantivy_version: (u32, u32, u32),
|
pub tantivy_version: (u32, u32, u32),
|
||||||
@@ -24,7 +26,7 @@ impl Footer {
|
|||||||
Footer {
|
Footer {
|
||||||
tantivy_version,
|
tantivy_version,
|
||||||
meta: format!(
|
meta: format!(
|
||||||
"tantivy {}.{}.{}, index v{}",
|
"tantivy v{}.{}.{}, index_format v{}",
|
||||||
tantivy_version.0,
|
tantivy_version.0,
|
||||||
tantivy_version.1,
|
tantivy_version.1,
|
||||||
tantivy_version.2,
|
tantivy_version.2,
|
||||||
@@ -34,6 +36,9 @@ impl Footer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serialises the footer to a byte-array
|
||||||
|
/// [ versioned_footer | meta | common_footer ]
|
||||||
|
/// [ 0..8 | 8..32 | 32..52 ]
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
let mut res = self.versioned_footer.to_bytes();
|
let mut res = self.versioned_footer.to_bytes();
|
||||||
res.extend_from_slice(self.meta.as_bytes());
|
res.extend_from_slice(self.meta.as_bytes());
|
||||||
@@ -64,20 +69,27 @@ impl Footer {
|
|||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::UnexpectedEof,
|
io::ErrorKind::UnexpectedEof,
|
||||||
format!(
|
format!(
|
||||||
"File corrupted. The footer len is {}, while the entire file len is {}",
|
"The footer len is {}, while the entire file len is {}. \
|
||||||
|
Your index is either corrupted or was built using a tantivy version\
|
||||||
|
anterior to 0.11.",
|
||||||
size, len
|
size, len
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let footer = &data[len - size as usize..];
|
let footer = &data[len - size as usize..];
|
||||||
let meta_len = LittleEndian::read_u32(&footer[size - 20..]) as usize;
|
let meta_len = LittleEndian::read_u32(&footer[size - COMMON_FOOTER_SIZE..]) as usize;
|
||||||
let tantivy_major = LittleEndian::read_u32(&footer[size - 16..]);
|
let tantivy_major = LittleEndian::read_u32(&footer[size - 16..]);
|
||||||
let tantivy_minor = LittleEndian::read_u32(&footer[size - 12..]);
|
let tantivy_minor = LittleEndian::read_u32(&footer[size - 12..]);
|
||||||
let tantivy_patch = LittleEndian::read_u32(&footer[size - 8..]);
|
let tantivy_patch = LittleEndian::read_u32(&footer[size - 8..]);
|
||||||
Ok(Footer {
|
Ok(Footer {
|
||||||
tantivy_version: (tantivy_major, tantivy_minor, tantivy_patch),
|
tantivy_version: (tantivy_major, tantivy_minor, tantivy_patch),
|
||||||
meta: String::from_utf8_lossy(&footer[size - meta_len - 20..size - 20]).into_owned(),
|
meta: String::from_utf8_lossy(
|
||||||
versioned_footer: VersionedFooter::from_bytes(&footer[..size - meta_len - 20])?,
|
&footer[size - meta_len - COMMON_FOOTER_SIZE..size - COMMON_FOOTER_SIZE],
|
||||||
|
)
|
||||||
|
.into_owned(),
|
||||||
|
versioned_footer: VersionedFooter::from_bytes(
|
||||||
|
&footer[..size - meta_len - COMMON_FOOTER_SIZE],
|
||||||
|
)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,24 +100,28 @@ impl Footer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> usize {
|
pub fn size(&self) -> usize {
|
||||||
self.versioned_footer.size() as usize + self.meta.len() + 20
|
self.versioned_footer.size() as usize + self.meta.len() + COMMON_FOOTER_SIZE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Footer that includes a crc32 hash that enables us to checksum files in the index
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum VersionedFooter {
|
pub enum VersionedFooter {
|
||||||
UnknownVersion { version: u32, size: u32 },
|
UnknownVersion { version: u32, size: u32 },
|
||||||
V0(u32), // crc
|
V0(CrcHashU32), // crc
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VersionedFooter {
|
impl VersionedFooter {
|
||||||
|
/// Serializes a valid `VersionedFooter` or panics if the version is unknown
|
||||||
|
/// [ version | crc_hash ]
|
||||||
|
/// [ 0..4 | 4..8 ]
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
match self {
|
match self {
|
||||||
VersionedFooter::V0(crc) => {
|
VersionedFooter::V0(crc) => {
|
||||||
let mut res = vec![0; 8];
|
let mut buf = [0u8; 8];
|
||||||
LittleEndian::write_u32(&mut res, 0);
|
LittleEndian::write_u32(&mut buf[0..4], 0);
|
||||||
LittleEndian::write_u32(&mut res[4..], *crc);
|
LittleEndian::write_u32(&mut buf[4..8], *crc);
|
||||||
res
|
buf.to_vec()
|
||||||
}
|
}
|
||||||
VersionedFooter::UnknownVersion { .. } => {
|
VersionedFooter::UnknownVersion { .. } => {
|
||||||
panic!("Unsupported index should never get serialized");
|
panic!("Unsupported index should never get serialized");
|
||||||
@@ -115,20 +131,26 @@ impl VersionedFooter {
|
|||||||
|
|
||||||
pub fn from_bytes(footer: &[u8]) -> Result<Self, io::Error> {
|
pub fn from_bytes(footer: &[u8]) -> Result<Self, io::Error> {
|
||||||
assert!(footer.len() >= 4);
|
assert!(footer.len() >= 4);
|
||||||
|
if footer.len() < 4 {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
"Footer should be more than 4 bytes.",
|
||||||
|
));
|
||||||
|
}
|
||||||
let version = LittleEndian::read_u32(footer);
|
let version = LittleEndian::read_u32(footer);
|
||||||
match version {
|
match version {
|
||||||
|
// the first 4 bytes should be zeroed out thus returning a `0`
|
||||||
0 => {
|
0 => {
|
||||||
if footer.len() == 8 {
|
if footer.len() != 8 {
|
||||||
Ok(VersionedFooter::V0(LittleEndian::read_u32(&footer[4..])))
|
return Err(io::Error::new(
|
||||||
} else {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::UnexpectedEof,
|
io::ErrorKind::UnexpectedEof,
|
||||||
format!(
|
format!(
|
||||||
"File corrupted. The versioned footer len is {}, while it should be 8",
|
"File corrupted. The versioned footer len is {}, while it should be 8",
|
||||||
footer.len()
|
footer.len()
|
||||||
),
|
),
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
|
Ok(VersionedFooter::V0(LittleEndian::read_u32(&footer[4..])))
|
||||||
}
|
}
|
||||||
version => Ok(VersionedFooter::UnknownVersion {
|
version => Ok(VersionedFooter::UnknownVersion {
|
||||||
version,
|
version,
|
||||||
@@ -151,7 +173,7 @@ impl VersionedFooter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn crc(&self) -> Option<u32> {
|
pub fn crc(&self) -> Option<CrcHashU32> {
|
||||||
match self {
|
match self {
|
||||||
VersionedFooter::V0(crc) => Some(*crc),
|
VersionedFooter::V0(crc) => Some(*crc),
|
||||||
VersionedFooter::UnknownVersion { .. } => None,
|
VersionedFooter::UnknownVersion { .. } => None,
|
||||||
@@ -190,7 +212,6 @@ impl<W: TerminatingWrite> Write for FooterProxy<W> {
|
|||||||
impl<W: TerminatingWrite> TerminatingWrite for FooterProxy<W> {
|
impl<W: TerminatingWrite> TerminatingWrite for FooterProxy<W> {
|
||||||
fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()> {
|
fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()> {
|
||||||
let crc = self.hasher.take().unwrap().finalize();
|
let crc = self.hasher.take().unwrap().finalize();
|
||||||
|
|
||||||
let footer = Footer::new(VersionedFooter::V0(crc)).to_bytes();
|
let footer = Footer::new(VersionedFooter::V0(crc)).to_bytes();
|
||||||
let mut writer = self.writer.take().unwrap();
|
let mut writer = self.writer.take().unwrap();
|
||||||
writer.write_all(&footer)?;
|
writer.write_all(&footer)?;
|
||||||
@@ -200,14 +221,57 @@ impl<W: TerminatingWrite> TerminatingWrite for FooterProxy<W> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::directory::footer::{Footer, VersionedFooter};
|
use crate::directory::footer::{Footer, VersionedFooter};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialize_deserialize_footer() {
|
fn test_serialize_deserialize_footer() {
|
||||||
let crc = 123456;
|
let crc = 123456;
|
||||||
let footer = Footer::new(VersionedFooter::V0(crc));
|
let footer = Footer::new(VersionedFooter::V0(crc));
|
||||||
let footer_bytes = footer.to_bytes();
|
let footer_bytes = footer.to_bytes();
|
||||||
|
|
||||||
assert_eq!(Footer::from_bytes(&footer_bytes).unwrap(), footer);
|
assert_eq!(Footer::from_bytes(&footer_bytes).unwrap(), footer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn footer_length() {
|
||||||
|
// test to make sure the ascii art in the doc-strings is correct
|
||||||
|
let crc = 1111111 as u32;
|
||||||
|
let versioned_footer = VersionedFooter::V0(crc);
|
||||||
|
assert_eq!(versioned_footer.size(), 8);
|
||||||
|
let footer = Footer::new(versioned_footer);
|
||||||
|
let regex_ptn = Regex::new(
|
||||||
|
"tantivy v[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.{0,10}, index_format v[0-9]{1,5}",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(regex_ptn.find(&footer.meta).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn versioned_footer_from_bytes() {
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
let v_footer_bytes = vec![0, 0, 0, 0, 12, 35, 89, 18];
|
||||||
|
let versioned_footer = VersionedFooter::from_bytes(&v_footer_bytes).unwrap();
|
||||||
|
let expected_versioned_footer =
|
||||||
|
VersionedFooter::V0(LittleEndian::read_u32(&[12, 35, 89, 18]));
|
||||||
|
assert_eq!(versioned_footer, expected_versioned_footer);
|
||||||
|
|
||||||
|
assert_eq!(versioned_footer.to_bytes(), v_footer_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[should_panic(expected = "Unsupported index should never get serialized")]
|
||||||
|
#[test]
|
||||||
|
fn versioned_footer_panic() {
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
let v_footer_bytes = vec![1; 8];
|
||||||
|
let versioned_footer = VersionedFooter::from_bytes(&v_footer_bytes).unwrap();
|
||||||
|
let expected_version = LittleEndian::read_u32(&[1, 1, 1, 1]);
|
||||||
|
let expected_versioned_footer = VersionedFooter::UnknownVersion {
|
||||||
|
version: expected_version,
|
||||||
|
size: v_footer_bytes.len() as u32,
|
||||||
|
};
|
||||||
|
assert_eq!(versioned_footer, expected_versioned_footer);
|
||||||
|
|
||||||
|
versioned_footer.to_bytes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::core::MANAGED_FILEPATH;
|
|||||||
use crate::directory::error::{DeleteError, IOError, LockError, OpenReadError, OpenWriteError};
|
use crate::directory::error::{DeleteError, IOError, LockError, OpenReadError, OpenWriteError};
|
||||||
use crate::directory::footer::{Footer, FooterProxy};
|
use crate::directory::footer::{Footer, FooterProxy};
|
||||||
use crate::directory::DirectoryLock;
|
use crate::directory::DirectoryLock;
|
||||||
|
use crate::directory::GarbageCollectionResult;
|
||||||
use crate::directory::Lock;
|
use crate::directory::Lock;
|
||||||
use crate::directory::META_LOCK;
|
use crate::directory::META_LOCK;
|
||||||
use crate::directory::{ReadOnlySource, WritePtr};
|
use crate::directory::{ReadOnlySource, WritePtr};
|
||||||
@@ -104,7 +105,10 @@ impl ManagedDirectory {
|
|||||||
/// If a file cannot be deleted (for permission reasons for instance)
|
/// If a file cannot be deleted (for permission reasons for instance)
|
||||||
/// an error is simply logged, and the file remains in the list of managed
|
/// an error is simply logged, and the file remains in the list of managed
|
||||||
/// files.
|
/// files.
|
||||||
pub fn garbage_collect<L: FnOnce() -> HashSet<PathBuf>>(&mut self, get_living_files: L) {
|
pub fn garbage_collect<L: FnOnce() -> HashSet<PathBuf>>(
|
||||||
|
&mut self,
|
||||||
|
get_living_files: L,
|
||||||
|
) -> crate::Result<GarbageCollectionResult> {
|
||||||
info!("Garbage collect");
|
info!("Garbage collect");
|
||||||
let mut files_to_delete = vec![];
|
let mut files_to_delete = vec![];
|
||||||
|
|
||||||
@@ -130,19 +134,25 @@ impl ManagedDirectory {
|
|||||||
// 2) writer change meta.json (for instance after a merge or a commit)
|
// 2) writer change meta.json (for instance after a merge or a commit)
|
||||||
// 3) gc kicks in.
|
// 3) gc kicks in.
|
||||||
// 4) gc removes a file that was useful for process B, before process B opened it.
|
// 4) gc removes a file that was useful for process B, before process B opened it.
|
||||||
if let Ok(_meta_lock) = self.acquire_lock(&META_LOCK) {
|
match self.acquire_lock(&META_LOCK) {
|
||||||
let living_files = get_living_files();
|
Ok(_meta_lock) => {
|
||||||
for managed_path in &meta_informations_rlock.managed_paths {
|
let living_files = get_living_files();
|
||||||
if !living_files.contains(managed_path) {
|
for managed_path in &meta_informations_rlock.managed_paths {
|
||||||
files_to_delete.push(managed_path.clone());
|
if !living_files.contains(managed_path) {
|
||||||
|
files_to_delete.push(managed_path.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
Err(err) => {
|
||||||
error!("Failed to acquire lock for GC");
|
error!("Failed to acquire lock for GC");
|
||||||
|
return Err(crate::Error::from(err));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut failed_to_delete_files = vec![];
|
||||||
let mut deleted_files = vec![];
|
let mut deleted_files = vec![];
|
||||||
|
|
||||||
for file_to_delete in files_to_delete {
|
for file_to_delete in files_to_delete {
|
||||||
match self.delete(&file_to_delete) {
|
match self.delete(&file_to_delete) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@@ -152,9 +162,10 @@ impl ManagedDirectory {
|
|||||||
Err(file_error) => {
|
Err(file_error) => {
|
||||||
match file_error {
|
match file_error {
|
||||||
DeleteError::FileDoesNotExist(_) => {
|
DeleteError::FileDoesNotExist(_) => {
|
||||||
deleted_files.push(file_to_delete);
|
deleted_files.push(file_to_delete.clone());
|
||||||
}
|
}
|
||||||
DeleteError::IOError(_) => {
|
DeleteError::IOError(_) => {
|
||||||
|
failed_to_delete_files.push(file_to_delete.clone());
|
||||||
if !cfg!(target_os = "windows") {
|
if !cfg!(target_os = "windows") {
|
||||||
// On windows, delete is expected to fail if the file
|
// On windows, delete is expected to fail if the file
|
||||||
// is mmapped.
|
// is mmapped.
|
||||||
@@ -177,10 +188,13 @@ impl ManagedDirectory {
|
|||||||
for delete_file in &deleted_files {
|
for delete_file in &deleted_files {
|
||||||
managed_paths_write.remove(delete_file);
|
managed_paths_write.remove(delete_file);
|
||||||
}
|
}
|
||||||
if save_managed_paths(self.directory.as_mut(), &meta_informations_wlock).is_err() {
|
save_managed_paths(self.directory.as_mut(), &meta_informations_wlock)?;
|
||||||
error!("Failed to save the list of managed files.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(GarbageCollectionResult {
|
||||||
|
deleted_files,
|
||||||
|
failed_to_delete_files,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers a file as managed
|
/// Registers a file as managed
|
||||||
@@ -328,7 +342,7 @@ mod tests_mmap_specific {
|
|||||||
assert!(managed_directory.exists(test_path1));
|
assert!(managed_directory.exists(test_path1));
|
||||||
assert!(managed_directory.exists(test_path2));
|
assert!(managed_directory.exists(test_path2));
|
||||||
let living_files: HashSet<PathBuf> = [test_path1.to_owned()].iter().cloned().collect();
|
let living_files: HashSet<PathBuf> = [test_path1.to_owned()].iter().cloned().collect();
|
||||||
managed_directory.garbage_collect(|| living_files);
|
assert!(managed_directory.garbage_collect(|| living_files).is_ok());
|
||||||
assert!(managed_directory.exists(test_path1));
|
assert!(managed_directory.exists(test_path1));
|
||||||
assert!(!managed_directory.exists(test_path2));
|
assert!(!managed_directory.exists(test_path2));
|
||||||
}
|
}
|
||||||
@@ -338,7 +352,7 @@ mod tests_mmap_specific {
|
|||||||
assert!(managed_directory.exists(test_path1));
|
assert!(managed_directory.exists(test_path1));
|
||||||
assert!(!managed_directory.exists(test_path2));
|
assert!(!managed_directory.exists(test_path2));
|
||||||
let living_files: HashSet<PathBuf> = HashSet::new();
|
let living_files: HashSet<PathBuf> = HashSet::new();
|
||||||
managed_directory.garbage_collect(|| living_files);
|
assert!(managed_directory.garbage_collect(|| living_files).is_ok());
|
||||||
assert!(!managed_directory.exists(test_path1));
|
assert!(!managed_directory.exists(test_path1));
|
||||||
assert!(!managed_directory.exists(test_path2));
|
assert!(!managed_directory.exists(test_path2));
|
||||||
}
|
}
|
||||||
@@ -360,7 +374,9 @@ mod tests_mmap_specific {
|
|||||||
assert!(managed_directory.exists(test_path1));
|
assert!(managed_directory.exists(test_path1));
|
||||||
|
|
||||||
let _mmap_read = managed_directory.open_read(test_path1).unwrap();
|
let _mmap_read = managed_directory.open_read(test_path1).unwrap();
|
||||||
managed_directory.garbage_collect(|| living_files.clone());
|
assert!(managed_directory
|
||||||
|
.garbage_collect(|| living_files.clone())
|
||||||
|
.is_ok());
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
// On Windows, gc should try and fail the file as it is mmapped.
|
// On Windows, gc should try and fail the file as it is mmapped.
|
||||||
assert!(managed_directory.exists(test_path1));
|
assert!(managed_directory.exists(test_path1));
|
||||||
@@ -368,7 +384,7 @@ mod tests_mmap_specific {
|
|||||||
drop(_mmap_read);
|
drop(_mmap_read);
|
||||||
// The file should still be in the list of managed file and
|
// The file should still be in the list of managed file and
|
||||||
// eventually be deleted once mmap is released.
|
// eventually be deleted once mmap is released.
|
||||||
managed_directory.garbage_collect(|| living_files);
|
assert!(managed_directory.garbage_collect(|| living_files).is_ok());
|
||||||
assert!(!managed_directory.exists(test_path1));
|
assert!(!managed_directory.exists(test_path1));
|
||||||
} else {
|
} else {
|
||||||
assert!(!managed_directory.exists(test_path1));
|
assert!(!managed_directory.exists(test_path1));
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ impl WatcherWrapper {
|
|||||||
// We might want to be more accurate than this at one point.
|
// We might want to be more accurate than this at one point.
|
||||||
if let Some(filename) = changed_path.file_name() {
|
if let Some(filename) = changed_path.file_name() {
|
||||||
if filename == *META_FILEPATH {
|
if filename == *META_FILEPATH {
|
||||||
watcher_router_clone.broadcast();
|
let _ = watcher_router_clone.broadcast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -538,16 +538,15 @@ mod tests {
|
|||||||
// The following tests are specific to the MmapDirectory
|
// The following tests are specific to the MmapDirectory
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::indexer::LogMergePolicy;
|
||||||
use crate::schema::{Schema, SchemaBuilder, TEXT};
|
use crate::schema::{Schema, SchemaBuilder, TEXT};
|
||||||
use crate::Index;
|
use crate::Index;
|
||||||
use crate::ReloadPolicy;
|
use crate::ReloadPolicy;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_open_non_existant_path() {
|
fn test_open_non_existent_path() {
|
||||||
assert!(MmapDirectory::open(PathBuf::from("./nowhere")).is_err());
|
assert!(MmapDirectory::open(PathBuf::from("./nowhere")).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,13 +639,18 @@ mod tests {
|
|||||||
let tmp_dir = tempfile::TempDir::new().unwrap();
|
let tmp_dir = tempfile::TempDir::new().unwrap();
|
||||||
let tmp_dirpath = tmp_dir.path().to_owned();
|
let tmp_dirpath = tmp_dir.path().to_owned();
|
||||||
let mut watch_wrapper = WatcherWrapper::new(&tmp_dirpath).unwrap();
|
let mut watch_wrapper = WatcherWrapper::new(&tmp_dirpath).unwrap();
|
||||||
let tmp_file = tmp_dirpath.join("coucou");
|
let tmp_file = tmp_dirpath.join(*META_FILEPATH);
|
||||||
let _handle = watch_wrapper.watch(Box::new(move || {
|
let _handle = watch_wrapper.watch(Box::new(move || {
|
||||||
counter_clone.fetch_add(1, Ordering::SeqCst);
|
counter_clone.fetch_add(1, Ordering::SeqCst);
|
||||||
}));
|
}));
|
||||||
|
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||||
|
let _handle2 = watch_wrapper.watch(Box::new(move || {
|
||||||
|
let _ = sender.send(());
|
||||||
|
}));
|
||||||
assert_eq!(counter.load(Ordering::SeqCst), 0);
|
assert_eq!(counter.load(Ordering::SeqCst), 0);
|
||||||
fs::write(&tmp_file, b"whateverwilldo").unwrap();
|
fs::write(&tmp_file, b"whateverwilldo").unwrap();
|
||||||
thread::sleep(Duration::new(0, 1_000u32));
|
assert!(receiver.recv().is_ok());
|
||||||
|
assert!(counter.load(Ordering::SeqCst) >= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -655,34 +659,42 @@ mod tests {
|
|||||||
let mut schema_builder: SchemaBuilder = Schema::builder();
|
let mut schema_builder: SchemaBuilder = Schema::builder();
|
||||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||||
let schema = schema_builder.build();
|
let schema = schema_builder.build();
|
||||||
|
|
||||||
{
|
{
|
||||||
let index = Index::create(mmap_directory.clone(), schema).unwrap();
|
let index = Index::create(mmap_directory.clone(), schema).unwrap();
|
||||||
|
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
||||||
for _num_commits in 0..16 {
|
let mut log_merge_policy = LogMergePolicy::default();
|
||||||
|
log_merge_policy.set_min_merge_size(3);
|
||||||
|
index_writer.set_merge_policy(Box::new(log_merge_policy));
|
||||||
|
for _num_commits in 0..10 {
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
index_writer.add_document(doc!(text_field=>"abc"));
|
index_writer.add_document(doc!(text_field=>"abc"));
|
||||||
}
|
}
|
||||||
index_writer.commit().unwrap();
|
index_writer.commit().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let reader = index
|
let reader = index
|
||||||
.reader_builder()
|
.reader_builder()
|
||||||
.reload_policy(ReloadPolicy::Manual)
|
.reload_policy(ReloadPolicy::Manual)
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
for _ in 0..30 {
|
|
||||||
|
for _ in 0..4 {
|
||||||
index_writer.add_document(doc!(text_field=>"abc"));
|
index_writer.add_document(doc!(text_field=>"abc"));
|
||||||
index_writer.commit().unwrap();
|
index_writer.commit().unwrap();
|
||||||
reader.reload().unwrap();
|
reader.reload().unwrap();
|
||||||
}
|
}
|
||||||
index_writer.wait_merging_threads().unwrap();
|
index_writer.wait_merging_threads().unwrap();
|
||||||
|
|
||||||
reader.reload().unwrap();
|
reader.reload().unwrap();
|
||||||
let num_segments = reader.searcher().segment_readers().len();
|
let num_segments = reader.searcher().segment_readers().len();
|
||||||
assert_eq!(num_segments, 4);
|
assert!(num_segments <= 4);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
num_segments * 7,
|
num_segments * 7,
|
||||||
mmap_directory.get_cache_info().mmapped.len()
|
mmap_directory.get_cache_info().mmapped.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(mmap_directory.get_cache_info().mmapped.len(), 0);
|
assert!(mmap_directory.get_cache_info().mmapped.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,21 @@ pub use self::read_only_source::ReadOnlySource;
|
|||||||
pub(crate) use self::watch_event_router::WatchCallbackList;
|
pub(crate) use self::watch_event_router::WatchCallbackList;
|
||||||
pub use self::watch_event_router::{WatchCallback, WatchHandle};
|
pub use self::watch_event_router::{WatchCallback, WatchHandle};
|
||||||
use std::io::{self, BufWriter, Write};
|
use std::io::{self, BufWriter, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Outcome of the Garbage collection
|
||||||
|
pub struct GarbageCollectionResult {
|
||||||
|
/// List of files that were deleted in this cycle
|
||||||
|
pub deleted_files: Vec<PathBuf>,
|
||||||
|
/// List of files that were schedule to be deleted in this cycle,
|
||||||
|
/// but deletion did not work. This typically happens on windows,
|
||||||
|
/// as deleting a memory mapped file is forbidden.
|
||||||
|
///
|
||||||
|
/// If a searcher is still held, a file cannot be deleted.
|
||||||
|
/// This is not considered a bug, the file will simply be deleted
|
||||||
|
/// in the next GC.
|
||||||
|
pub failed_to_delete_files: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mmap")]
|
#[cfg(feature = "mmap")]
|
||||||
pub use self::mmap_directory::MmapDirectory;
|
pub use self::mmap_directory::MmapDirectory;
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ impl Directory for RAMDirectory {
|
|||||||
vec_writer.write_all(data)?;
|
vec_writer.write_all(data)?;
|
||||||
vec_writer.flush()?;
|
vec_writer.flush()?;
|
||||||
if path == Path::new(&*META_FILEPATH) {
|
if path == Path::new(&*META_FILEPATH) {
|
||||||
self.fs.write().unwrap().watch_router.broadcast();
|
let _ = self.fs.write().unwrap().watch_router.broadcast();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,117 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use futures::channel::oneshot;
|
||||||
|
use futures::executor::block_on;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::Ordering::SeqCst;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::{AtomicBool, AtomicUsize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
|
||||||
use std::time;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[test]
|
#[cfg(feature = "mmap")]
|
||||||
fn test_ram_directory() {
|
mod mmap_directory_tests {
|
||||||
let mut ram_directory = RAMDirectory::create();
|
use crate::directory::MmapDirectory;
|
||||||
test_directory(&mut ram_directory);
|
|
||||||
|
type DirectoryImpl = MmapDirectory;
|
||||||
|
|
||||||
|
fn make_directory() -> DirectoryImpl {
|
||||||
|
MmapDirectory::create_from_tempdir().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_simple(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_create_the_file() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_write_create_the_file(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rewrite_forbidden() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_rewrite_forbidden(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_directory_delete() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_directory_delete(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lock_non_blocking() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_lock_non_blocking(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lock_blocking() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_lock_blocking(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_watch() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_watch(&mut directory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
mod ram_directory_tests {
|
||||||
#[cfg(feature = "mmap")]
|
use crate::directory::RAMDirectory;
|
||||||
fn test_mmap_directory() {
|
|
||||||
let mut mmap_directory = MmapDirectory::create_from_tempdir().unwrap();
|
type DirectoryImpl = RAMDirectory;
|
||||||
test_directory(&mut mmap_directory);
|
|
||||||
|
fn make_directory() -> DirectoryImpl {
|
||||||
|
RAMDirectory::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_simple(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_create_the_file() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_write_create_the_file(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rewrite_forbidden() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_rewrite_forbidden(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_directory_delete() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_directory_delete(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lock_non_blocking() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_lock_non_blocking(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lock_blocking() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_lock_blocking(&mut directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_watch() {
|
||||||
|
let mut directory = make_directory();
|
||||||
|
super::test_watch(&mut directory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -99,48 +191,39 @@ fn test_directory_delete(directory: &mut dyn Directory) {
|
|||||||
assert!(directory.delete(&test_path).is_err());
|
assert!(directory.delete(&test_path).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_directory(directory: &mut dyn Directory) {
|
|
||||||
test_simple(directory);
|
|
||||||
test_rewrite_forbidden(directory);
|
|
||||||
test_write_create_the_file(directory);
|
|
||||||
test_directory_delete(directory);
|
|
||||||
test_lock_non_blocking(directory);
|
|
||||||
test_lock_blocking(directory);
|
|
||||||
test_watch(directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_watch(directory: &mut dyn Directory) {
|
fn test_watch(directory: &mut dyn Directory) {
|
||||||
|
let num_progress: Arc<AtomicUsize> = Default::default();
|
||||||
let counter: Arc<AtomicUsize> = Default::default();
|
let counter: Arc<AtomicUsize> = Default::default();
|
||||||
let counter_clone = counter.clone();
|
let counter_clone = counter.clone();
|
||||||
|
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||||
let watch_callback = Box::new(move || {
|
let watch_callback = Box::new(move || {
|
||||||
counter_clone.fetch_add(1, Ordering::SeqCst);
|
counter_clone.fetch_add(1, SeqCst);
|
||||||
});
|
});
|
||||||
assert!(directory
|
// This callback is used to synchronize watching in our unit test.
|
||||||
.atomic_write(Path::new("meta.json"), b"random_test_data")
|
// We bind it to a variable because the callback is removed when that
|
||||||
.is_ok());
|
// handle is dropped.
|
||||||
thread::sleep(Duration::new(0, 10_000));
|
|
||||||
assert_eq!(0, counter.load(Ordering::SeqCst));
|
|
||||||
|
|
||||||
let watch_handle = directory.watch(watch_callback).unwrap();
|
let watch_handle = directory.watch(watch_callback).unwrap();
|
||||||
|
let _progress_listener = directory
|
||||||
|
.watch(Box::new(move || {
|
||||||
|
let val = num_progress.fetch_add(1, SeqCst);
|
||||||
|
let _ = sender.send(val);
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
for i in 0..10 {
|
for i in 0..10 {
|
||||||
assert_eq!(i, counter.load(Ordering::SeqCst));
|
assert_eq!(i, counter.load(SeqCst));
|
||||||
assert!(directory
|
assert!(directory
|
||||||
.atomic_write(Path::new("meta.json"), b"random_test_data_2")
|
.atomic_write(Path::new("meta.json"), b"random_test_data_2")
|
||||||
.is_ok());
|
.is_ok());
|
||||||
for _ in 0..1_000 {
|
assert_eq!(receiver.recv_timeout(Duration::from_millis(500)), Ok(i));
|
||||||
if counter.load(Ordering::SeqCst) > i {
|
assert_eq!(i + 1, counter.load(SeqCst));
|
||||||
break;
|
|
||||||
}
|
|
||||||
thread::sleep(Duration::from_millis(10));
|
|
||||||
}
|
|
||||||
assert_eq!(i + 1, counter.load(Ordering::SeqCst));
|
|
||||||
}
|
}
|
||||||
mem::drop(watch_handle);
|
mem::drop(watch_handle);
|
||||||
assert!(directory
|
assert!(directory
|
||||||
.atomic_write(Path::new("meta.json"), b"random_test_data")
|
.atomic_write(Path::new("meta.json"), b"random_test_data")
|
||||||
.is_ok());
|
.is_ok());
|
||||||
thread::sleep(Duration::from_millis(200));
|
assert!(receiver.recv_timeout(Duration::from_millis(500)).is_ok());
|
||||||
assert_eq!(10, counter.load(Ordering::SeqCst));
|
assert_eq!(10, counter.load(SeqCst));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_lock_non_blocking(directory: &mut dyn Directory) {
|
fn test_lock_non_blocking(directory: &mut dyn Directory) {
|
||||||
@@ -174,9 +257,13 @@ fn test_lock_blocking(directory: &mut dyn Directory) {
|
|||||||
is_blocking: true,
|
is_blocking: true,
|
||||||
});
|
});
|
||||||
assert!(lock_a_res.is_ok());
|
assert!(lock_a_res.is_ok());
|
||||||
|
let in_thread = Arc::new(AtomicBool::default());
|
||||||
|
let in_thread_clone = in_thread.clone();
|
||||||
|
let (sender, receiver) = oneshot::channel();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
//< lock_a_res is sent to the thread.
|
//< lock_a_res is sent to the thread.
|
||||||
std::thread::sleep(time::Duration::from_millis(10));
|
in_thread_clone.store(true, SeqCst);
|
||||||
|
let _just_sync = block_on(receiver);
|
||||||
// explicitely droping lock_a_res. It would have been sufficient to just force it
|
// explicitely droping lock_a_res. It would have been sufficient to just force it
|
||||||
// to be part of the move, but the intent seems clearer that way.
|
// to be part of the move, but the intent seems clearer that way.
|
||||||
drop(lock_a_res);
|
drop(lock_a_res);
|
||||||
@@ -189,14 +276,18 @@ fn test_lock_blocking(directory: &mut dyn Directory) {
|
|||||||
});
|
});
|
||||||
assert!(lock_a_res.is_err());
|
assert!(lock_a_res.is_err());
|
||||||
}
|
}
|
||||||
{
|
let directory_clone = directory.box_clone();
|
||||||
// the blocking call should wait for at least 10ms.
|
let (sender2, receiver2) = oneshot::channel();
|
||||||
let start = time::Instant::now();
|
let join_handle = std::thread::spawn(move || {
|
||||||
let lock_a_res = directory.acquire_lock(&Lock {
|
assert!(sender2.send(()).is_ok());
|
||||||
|
let lock_a_res = directory_clone.acquire_lock(&Lock {
|
||||||
filepath: PathBuf::from("a.lock"),
|
filepath: PathBuf::from("a.lock"),
|
||||||
is_blocking: true,
|
is_blocking: true,
|
||||||
});
|
});
|
||||||
|
assert!(in_thread.load(SeqCst));
|
||||||
assert!(lock_a_res.is_ok());
|
assert!(lock_a_res.is_ok());
|
||||||
assert!(start.elapsed().subsec_millis() >= 10);
|
});
|
||||||
}
|
assert!(block_on(receiver2).is_ok());
|
||||||
|
assert!(sender.send(()).is_ok());
|
||||||
|
assert!(join_handle.join().is_ok());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use futures::channel::oneshot;
|
||||||
|
use futures::{Future, TryFutureExt};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
@@ -47,14 +49,21 @@ impl WatchCallbackList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Triggers all callbacks
|
/// Triggers all callbacks
|
||||||
pub fn broadcast(&self) {
|
pub fn broadcast(&self) -> impl Future<Output = ()> {
|
||||||
let callbacks = self.list_callback();
|
let callbacks = self.list_callback();
|
||||||
|
let (sender, receiver) = oneshot::channel();
|
||||||
|
let result = receiver.unwrap_or_else(|_| ());
|
||||||
|
if callbacks.is_empty() {
|
||||||
|
let _ = sender.send(());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
let spawn_res = std::thread::Builder::new()
|
let spawn_res = std::thread::Builder::new()
|
||||||
.name("watch-callbacks".to_string())
|
.name("watch-callbacks".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
for callback in callbacks {
|
for callback in callbacks {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
let _ = sender.send(());
|
||||||
});
|
});
|
||||||
if let Err(err) = spawn_res {
|
if let Err(err) = spawn_res {
|
||||||
error!(
|
error!(
|
||||||
@@ -62,19 +71,17 @@ impl WatchCallbackList {
|
|||||||
err
|
err
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::directory::WatchCallbackList;
|
use crate::directory::WatchCallbackList;
|
||||||
|
use futures::executor::block_on;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
const WAIT_TIME: u64 = 20;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_watch_event_router_simple() {
|
fn test_watch_event_router_simple() {
|
||||||
@@ -84,22 +91,22 @@ mod tests {
|
|||||||
let inc_callback = Box::new(move || {
|
let inc_callback = Box::new(move || {
|
||||||
counter_clone.fetch_add(1, Ordering::SeqCst);
|
counter_clone.fetch_add(1, Ordering::SeqCst);
|
||||||
});
|
});
|
||||||
watch_event_router.broadcast();
|
block_on(watch_event_router.broadcast());
|
||||||
assert_eq!(0, counter.load(Ordering::SeqCst));
|
assert_eq!(0, counter.load(Ordering::SeqCst));
|
||||||
let handle_a = watch_event_router.subscribe(inc_callback);
|
let handle_a = watch_event_router.subscribe(inc_callback);
|
||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
|
||||||
assert_eq!(0, counter.load(Ordering::SeqCst));
|
assert_eq!(0, counter.load(Ordering::SeqCst));
|
||||||
watch_event_router.broadcast();
|
block_on(watch_event_router.broadcast());
|
||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
|
||||||
assert_eq!(1, counter.load(Ordering::SeqCst));
|
assert_eq!(1, counter.load(Ordering::SeqCst));
|
||||||
watch_event_router.broadcast();
|
block_on(async {
|
||||||
watch_event_router.broadcast();
|
(
|
||||||
watch_event_router.broadcast();
|
watch_event_router.broadcast().await,
|
||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
watch_event_router.broadcast().await,
|
||||||
|
watch_event_router.broadcast().await,
|
||||||
|
)
|
||||||
|
});
|
||||||
assert_eq!(4, counter.load(Ordering::SeqCst));
|
assert_eq!(4, counter.load(Ordering::SeqCst));
|
||||||
mem::drop(handle_a);
|
mem::drop(handle_a);
|
||||||
watch_event_router.broadcast();
|
block_on(watch_event_router.broadcast());
|
||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
|
||||||
assert_eq!(4, counter.load(Ordering::SeqCst));
|
assert_eq!(4, counter.load(Ordering::SeqCst));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,20 +122,20 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let handle_a = watch_event_router.subscribe(inc_callback(1));
|
let handle_a = watch_event_router.subscribe(inc_callback(1));
|
||||||
let handle_a2 = watch_event_router.subscribe(inc_callback(10));
|
let handle_a2 = watch_event_router.subscribe(inc_callback(10));
|
||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
|
||||||
assert_eq!(0, counter.load(Ordering::SeqCst));
|
assert_eq!(0, counter.load(Ordering::SeqCst));
|
||||||
watch_event_router.broadcast();
|
block_on(async {
|
||||||
watch_event_router.broadcast();
|
futures::join!(
|
||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
watch_event_router.broadcast(),
|
||||||
|
watch_event_router.broadcast()
|
||||||
|
)
|
||||||
|
});
|
||||||
assert_eq!(22, counter.load(Ordering::SeqCst));
|
assert_eq!(22, counter.load(Ordering::SeqCst));
|
||||||
mem::drop(handle_a);
|
mem::drop(handle_a);
|
||||||
watch_event_router.broadcast();
|
block_on(watch_event_router.broadcast());
|
||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
|
||||||
assert_eq!(32, counter.load(Ordering::SeqCst));
|
assert_eq!(32, counter.load(Ordering::SeqCst));
|
||||||
mem::drop(handle_a2);
|
mem::drop(handle_a2);
|
||||||
watch_event_router.broadcast();
|
block_on(watch_event_router.broadcast());
|
||||||
watch_event_router.broadcast();
|
block_on(watch_event_router.broadcast());
|
||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
|
||||||
assert_eq!(32, counter.load(Ordering::SeqCst));
|
assert_eq!(32, counter.load(Ordering::SeqCst));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,14 +149,15 @@ mod tests {
|
|||||||
});
|
});
|
||||||
let handle_a = watch_event_router.subscribe(inc_callback);
|
let handle_a = watch_event_router.subscribe(inc_callback);
|
||||||
assert_eq!(0, counter.load(Ordering::SeqCst));
|
assert_eq!(0, counter.load(Ordering::SeqCst));
|
||||||
watch_event_router.broadcast();
|
block_on(async {
|
||||||
watch_event_router.broadcast();
|
let future1 = watch_event_router.broadcast();
|
||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
let future2 = watch_event_router.broadcast();
|
||||||
|
futures::join!(future1, future2)
|
||||||
|
});
|
||||||
assert_eq!(2, counter.load(Ordering::SeqCst));
|
assert_eq!(2, counter.load(Ordering::SeqCst));
|
||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
|
||||||
mem::drop(handle_a);
|
mem::drop(handle_a);
|
||||||
watch_event_router.broadcast();
|
let _ = watch_event_router.broadcast();
|
||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
block_on(watch_event_router.broadcast());
|
||||||
assert_eq!(2, counter.load(Ordering::SeqCst));
|
assert_eq!(2, counter.load(Ordering::SeqCst));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use crate::core::SegmentComponent;
|
|||||||
use crate::core::SegmentId;
|
use crate::core::SegmentId;
|
||||||
use crate::core::SegmentMeta;
|
use crate::core::SegmentMeta;
|
||||||
use crate::core::SegmentReader;
|
use crate::core::SegmentReader;
|
||||||
use crate::directory::DirectoryLock;
|
|
||||||
use crate::directory::TerminatingWrite;
|
use crate::directory::TerminatingWrite;
|
||||||
|
use crate::directory::{DirectoryLock, GarbageCollectionResult};
|
||||||
use crate::docset::DocSet;
|
use crate::docset::DocSet;
|
||||||
use crate::error::TantivyError;
|
use crate::error::TantivyError;
|
||||||
use crate::fastfield::write_delete_bitset;
|
use crate::fastfield::write_delete_bitset;
|
||||||
@@ -23,10 +23,10 @@ use crate::schema::Document;
|
|||||||
use crate::schema::IndexRecordOption;
|
use crate::schema::IndexRecordOption;
|
||||||
use crate::schema::Term;
|
use crate::schema::Term;
|
||||||
use crate::Opstamp;
|
use crate::Opstamp;
|
||||||
use crate::Result;
|
|
||||||
use bit_set::BitSet;
|
use bit_set::BitSet;
|
||||||
use crossbeam::channel;
|
use crossbeam::channel;
|
||||||
use futures::{Canceled, Future};
|
use futures::executor::block_on;
|
||||||
|
use futures::future::Future;
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
@@ -72,7 +72,7 @@ pub struct IndexWriter {
|
|||||||
|
|
||||||
heap_size_in_bytes_per_thread: usize,
|
heap_size_in_bytes_per_thread: usize,
|
||||||
|
|
||||||
workers_join_handle: Vec<JoinHandle<Result<()>>>,
|
workers_join_handle: Vec<JoinHandle<crate::Result<()>>>,
|
||||||
|
|
||||||
operation_receiver: OperationReceiver,
|
operation_receiver: OperationReceiver,
|
||||||
operation_sender: OperationSender,
|
operation_sender: OperationSender,
|
||||||
@@ -95,7 +95,7 @@ fn compute_deleted_bitset(
|
|||||||
delete_cursor: &mut DeleteCursor,
|
delete_cursor: &mut DeleteCursor,
|
||||||
doc_opstamps: &DocToOpstampMapping,
|
doc_opstamps: &DocToOpstampMapping,
|
||||||
target_opstamp: Opstamp,
|
target_opstamp: Opstamp,
|
||||||
) -> Result<bool> {
|
) -> crate::Result<bool> {
|
||||||
let mut might_have_changed = false;
|
let mut might_have_changed = false;
|
||||||
while let Some(delete_op) = delete_cursor.get() {
|
while let Some(delete_op) = delete_cursor.get() {
|
||||||
if delete_op.opstamp > target_opstamp {
|
if delete_op.opstamp > target_opstamp {
|
||||||
@@ -132,7 +132,7 @@ pub(crate) fn advance_deletes(
|
|||||||
mut segment: Segment,
|
mut segment: Segment,
|
||||||
segment_entry: &mut SegmentEntry,
|
segment_entry: &mut SegmentEntry,
|
||||||
target_opstamp: Opstamp,
|
target_opstamp: Opstamp,
|
||||||
) -> Result<()> {
|
) -> crate::Result<()> {
|
||||||
{
|
{
|
||||||
if segment_entry.meta().delete_opstamp() == Some(target_opstamp) {
|
if segment_entry.meta().delete_opstamp() == Some(target_opstamp) {
|
||||||
// We are already up-to-date here.
|
// We are already up-to-date here.
|
||||||
@@ -181,7 +181,7 @@ fn index_documents(
|
|||||||
grouped_document_iterator: &mut dyn Iterator<Item = OperationGroup>,
|
grouped_document_iterator: &mut dyn Iterator<Item = OperationGroup>,
|
||||||
segment_updater: &mut SegmentUpdater,
|
segment_updater: &mut SegmentUpdater,
|
||||||
mut delete_cursor: DeleteCursor,
|
mut delete_cursor: DeleteCursor,
|
||||||
) -> Result<bool> {
|
) -> crate::Result<bool> {
|
||||||
let schema = segment.schema();
|
let schema = segment.schema();
|
||||||
|
|
||||||
let mut segment_writer = SegmentWriter::for_segment(memory_budget, segment.clone(), &schema)?;
|
let mut segment_writer = SegmentWriter::for_segment(memory_budget, segment.clone(), &schema)?;
|
||||||
@@ -227,7 +227,7 @@ fn index_documents(
|
|||||||
delete_cursor,
|
delete_cursor,
|
||||||
delete_bitset_opt,
|
delete_bitset_opt,
|
||||||
);
|
);
|
||||||
segment_updater.add_segment(segment_entry);
|
block_on(segment_updater.schedule_add_segment(segment_entry))?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ fn apply_deletes(
|
|||||||
mut delete_cursor: &mut DeleteCursor,
|
mut delete_cursor: &mut DeleteCursor,
|
||||||
doc_opstamps: &[Opstamp],
|
doc_opstamps: &[Opstamp],
|
||||||
last_docstamp: Opstamp,
|
last_docstamp: Opstamp,
|
||||||
) -> Result<Option<BitSet<u32>>> {
|
) -> crate::Result<Option<BitSet<u32>>> {
|
||||||
if delete_cursor.get().is_none() {
|
if delete_cursor.get().is_none() {
|
||||||
// if there are no delete operation in the queue, no need
|
// if there are no delete operation in the queue, no need
|
||||||
// to even open the segment.
|
// to even open the segment.
|
||||||
@@ -281,7 +281,7 @@ impl IndexWriter {
|
|||||||
num_threads: usize,
|
num_threads: usize,
|
||||||
heap_size_in_bytes_per_thread: usize,
|
heap_size_in_bytes_per_thread: usize,
|
||||||
directory_lock: DirectoryLock,
|
directory_lock: DirectoryLock,
|
||||||
) -> Result<IndexWriter> {
|
) -> crate::Result<IndexWriter> {
|
||||||
if heap_size_in_bytes_per_thread < HEAP_SIZE_MIN {
|
if heap_size_in_bytes_per_thread < HEAP_SIZE_MIN {
|
||||||
let err_msg = format!(
|
let err_msg = format!(
|
||||||
"The heap size per thread needs to be at least {}.",
|
"The heap size per thread needs to be at least {}.",
|
||||||
@@ -332,7 +332,7 @@ impl IndexWriter {
|
|||||||
|
|
||||||
/// If there are some merging threads, blocks until they all finish their work and
|
/// If there are some merging threads, blocks until they all finish their work and
|
||||||
/// then drop the `IndexWriter`.
|
/// then drop the `IndexWriter`.
|
||||||
pub fn wait_merging_threads(mut self) -> Result<()> {
|
pub fn wait_merging_threads(mut self) -> crate::Result<()> {
|
||||||
// this will stop the indexing thread,
|
// this will stop the indexing thread,
|
||||||
// dropping the last reference to the segment_updater.
|
// dropping the last reference to the segment_updater.
|
||||||
drop(self.operation_sender);
|
drop(self.operation_sender);
|
||||||
@@ -361,10 +361,10 @@ impl IndexWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn add_segment(&mut self, segment_meta: SegmentMeta) {
|
pub fn add_segment(&self, segment_meta: SegmentMeta) -> crate::Result<()> {
|
||||||
let delete_cursor = self.delete_queue.cursor();
|
let delete_cursor = self.delete_queue.cursor();
|
||||||
let segment_entry = SegmentEntry::new(segment_meta, delete_cursor, None);
|
let segment_entry = SegmentEntry::new(segment_meta, delete_cursor, None);
|
||||||
self.segment_updater.add_segment(segment_entry);
|
block_on(self.segment_updater.schedule_add_segment(segment_entry))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new segment.
|
/// Creates a new segment.
|
||||||
@@ -381,7 +381,7 @@ impl IndexWriter {
|
|||||||
|
|
||||||
/// Spawns a new worker thread for indexing.
|
/// Spawns a new worker thread for indexing.
|
||||||
/// The thread consumes documents from the pipeline.
|
/// The thread consumes documents from the pipeline.
|
||||||
fn add_indexing_worker(&mut self) -> Result<()> {
|
fn add_indexing_worker(&mut self) -> crate::Result<()> {
|
||||||
let document_receiver_clone = self.operation_receiver.clone();
|
let document_receiver_clone = self.operation_receiver.clone();
|
||||||
let mut segment_updater = self.segment_updater.clone();
|
let mut segment_updater = self.segment_updater.clone();
|
||||||
|
|
||||||
@@ -389,7 +389,7 @@ impl IndexWriter {
|
|||||||
|
|
||||||
let mem_budget = self.heap_size_in_bytes_per_thread;
|
let mem_budget = self.heap_size_in_bytes_per_thread;
|
||||||
let index = self.index.clone();
|
let index = self.index.clone();
|
||||||
let join_handle: JoinHandle<Result<()>> = thread::Builder::new()
|
let join_handle: JoinHandle<crate::Result<()>> = thread::Builder::new()
|
||||||
.name(format!("thrd-tantivy-index{}", self.worker_id))
|
.name(format!("thrd-tantivy-index{}", self.worker_id))
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
loop {
|
loop {
|
||||||
@@ -435,22 +435,23 @@ impl IndexWriter {
|
|||||||
self.segment_updater.get_merge_policy()
|
self.segment_updater.get_merge_policy()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the merge policy.
|
/// Setter for the merge policy.
|
||||||
pub fn set_merge_policy(&self, merge_policy: Box<dyn MergePolicy>) {
|
pub fn set_merge_policy(&self, merge_policy: Box<dyn MergePolicy>) {
|
||||||
self.segment_updater.set_merge_policy(merge_policy);
|
self.segment_updater.set_merge_policy(merge_policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_workers(&mut self) -> Result<()> {
|
fn start_workers(&mut self) -> crate::Result<()> {
|
||||||
for _ in 0..self.num_threads {
|
for _ in 0..self.num_threads {
|
||||||
self.add_indexing_worker()?;
|
self.add_indexing_worker()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detects and removes the files that
|
/// Detects and removes the files that are not used by the index anymore.
|
||||||
/// are not used by the index anymore.
|
pub fn garbage_collect_files(
|
||||||
pub fn garbage_collect_files(&mut self) -> Result<()> {
|
&self,
|
||||||
self.segment_updater.garbage_collect_files().wait()
|
) -> impl Future<Output = crate::Result<GarbageCollectionResult>> {
|
||||||
|
self.segment_updater.schedule_garbage_collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes all documents from the index
|
/// Deletes all documents from the index
|
||||||
@@ -489,7 +490,7 @@ impl IndexWriter {
|
|||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_all_documents(&mut self) -> Result<Opstamp> {
|
pub fn delete_all_documents(&self) -> crate::Result<Opstamp> {
|
||||||
// Delete segments
|
// Delete segments
|
||||||
self.segment_updater.remove_all_segments();
|
self.segment_updater.remove_all_segments();
|
||||||
// Return new stamp - reverted stamp
|
// Return new stamp - reverted stamp
|
||||||
@@ -500,11 +501,9 @@ impl IndexWriter {
|
|||||||
/// Merges a given list of segments
|
/// Merges a given list of segments
|
||||||
///
|
///
|
||||||
/// `segment_ids` is required to be non-empty.
|
/// `segment_ids` is required to be non-empty.
|
||||||
pub fn merge(
|
pub async fn merge(&mut self, segment_ids: &[SegmentId]) -> crate::Result<SegmentMeta> {
|
||||||
&mut self,
|
let merge_operation = self.segment_updater.make_merge_operation(segment_ids);
|
||||||
segment_ids: &[SegmentId],
|
self.segment_updater.start_merge(merge_operation)?.await
|
||||||
) -> Result<impl Future<Item = SegmentMeta, Error = Canceled>> {
|
|
||||||
self.segment_updater.start_merge(segment_ids)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Closes the current document channel send.
|
/// Closes the current document channel send.
|
||||||
@@ -530,13 +529,8 @@ impl IndexWriter {
|
|||||||
/// state as it was after the last commit.
|
/// state as it was after the last commit.
|
||||||
///
|
///
|
||||||
/// The opstamp at the last commit is returned.
|
/// The opstamp at the last commit is returned.
|
||||||
pub fn rollback(&mut self) -> Result<Opstamp> {
|
pub fn rollback(&mut self) -> crate::Result<Opstamp> {
|
||||||
info!("Rolling back to opstamp {}", self.committed_opstamp);
|
info!("Rolling back to opstamp {}", self.committed_opstamp);
|
||||||
self.rollback_impl()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Private, implementation of rollback
|
|
||||||
fn rollback_impl(&mut self) -> Result<Opstamp> {
|
|
||||||
// marks the segment updater as killed. From now on, all
|
// marks the segment updater as killed. From now on, all
|
||||||
// segment updates will be ignored.
|
// segment updates will be ignored.
|
||||||
self.segment_updater.kill();
|
self.segment_updater.kill();
|
||||||
@@ -592,7 +586,7 @@ impl IndexWriter {
|
|||||||
/// It is also possible to add a payload to the `commit`
|
/// It is also possible to add a payload to the `commit`
|
||||||
/// using this API.
|
/// using this API.
|
||||||
/// See [`PreparedCommit::set_payload()`](PreparedCommit.html)
|
/// See [`PreparedCommit::set_payload()`](PreparedCommit.html)
|
||||||
pub fn prepare_commit(&mut self) -> Result<PreparedCommit<'_>> {
|
pub fn prepare_commit(&mut self) -> crate::Result<PreparedCommit> {
|
||||||
// Here, because we join all of the worker threads,
|
// Here, because we join all of the worker threads,
|
||||||
// all of the segment update for this commit have been
|
// all of the segment update for this commit have been
|
||||||
// sent.
|
// sent.
|
||||||
@@ -639,7 +633,7 @@ impl IndexWriter {
|
|||||||
/// Commit returns the `opstamp` of the last document
|
/// Commit returns the `opstamp` of the last document
|
||||||
/// that made it in the commit.
|
/// that made it in the commit.
|
||||||
///
|
///
|
||||||
pub fn commit(&mut self) -> Result<Opstamp> {
|
pub fn commit(&mut self) -> crate::Result<Opstamp> {
|
||||||
self.prepare_commit()?.commit()
|
self.prepare_commit()?.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,9 +674,6 @@ impl IndexWriter {
|
|||||||
/// The opstamp is an increasing `u64` that can
|
/// The opstamp is an increasing `u64` that can
|
||||||
/// be used by the client to align commits with its own
|
/// be used by the client to align commits with its own
|
||||||
/// document queue.
|
/// document queue.
|
||||||
///
|
|
||||||
/// Currently it represents the number of documents that
|
|
||||||
/// have been added since the creation of the index.
|
|
||||||
pub fn add_document(&self, document: Document) -> Opstamp {
|
pub fn add_document(&self, document: Document) -> Opstamp {
|
||||||
let opstamp = self.stamper.stamp();
|
let opstamp = self.stamper.stamp();
|
||||||
let add_operation = AddOperation { opstamp, document };
|
let add_operation = AddOperation { opstamp, document };
|
||||||
|
|||||||
@@ -2,14 +2,23 @@ use crate::Opstamp;
|
|||||||
use crate::SegmentId;
|
use crate::SegmentId;
|
||||||
use census::{Inventory, TrackedObject};
|
use census::{Inventory, TrackedObject};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MergeOperationInventory(Inventory<InnerMergeOperation>);
|
pub(crate) struct MergeOperationInventory(Inventory<InnerMergeOperation>);
|
||||||
|
|
||||||
|
impl Deref for MergeOperationInventory {
|
||||||
|
type Target = Inventory<InnerMergeOperation>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MergeOperationInventory {
|
impl MergeOperationInventory {
|
||||||
pub fn segment_in_merge(&self) -> HashSet<SegmentId> {
|
pub fn segment_in_merge(&self) -> HashSet<SegmentId> {
|
||||||
let mut segment_in_merge = HashSet::default();
|
let mut segment_in_merge = HashSet::default();
|
||||||
for merge_op in self.0.list() {
|
for merge_op in self.list() {
|
||||||
for &segment_id in &merge_op.segment_ids {
|
for &segment_id in &merge_op.segment_ids {
|
||||||
segment_in_merge.insert(segment_id);
|
segment_in_merge.insert(segment_id);
|
||||||
}
|
}
|
||||||
@@ -35,13 +44,13 @@ pub struct MergeOperation {
|
|||||||
inner: TrackedObject<InnerMergeOperation>,
|
inner: TrackedObject<InnerMergeOperation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InnerMergeOperation {
|
pub(crate) struct InnerMergeOperation {
|
||||||
target_opstamp: Opstamp,
|
target_opstamp: Opstamp,
|
||||||
segment_ids: Vec<SegmentId>,
|
segment_ids: Vec<SegmentId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MergeOperation {
|
impl MergeOperation {
|
||||||
pub fn new(
|
pub(crate) fn new(
|
||||||
inventory: &MergeOperationInventory,
|
inventory: &MergeOperationInventory,
|
||||||
target_opstamp: Opstamp,
|
target_opstamp: Opstamp,
|
||||||
segment_ids: Vec<SegmentId>,
|
segment_ids: Vec<SegmentId>,
|
||||||
@@ -51,7 +60,7 @@ impl MergeOperation {
|
|||||||
segment_ids,
|
segment_ids,
|
||||||
};
|
};
|
||||||
MergeOperation {
|
MergeOperation {
|
||||||
inner: inventory.0.track(inner_merge_operation),
|
inner: inventory.track(inner_merge_operation),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -709,7 +709,7 @@ mod tests {
|
|||||||
use crate::IndexWriter;
|
use crate::IndexWriter;
|
||||||
use crate::Searcher;
|
use crate::Searcher;
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use futures::Future;
|
use futures::executor::block_on;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -792,11 +792,7 @@ mod tests {
|
|||||||
.searchable_segment_ids()
|
.searchable_segment_ids()
|
||||||
.expect("Searchable segments failed.");
|
.expect("Searchable segments failed.");
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
||||||
index_writer
|
block_on(index_writer.merge(&segment_ids)).expect("Merging failed");
|
||||||
.merge(&segment_ids)
|
|
||||||
.expect("Failed to initiate merge")
|
|
||||||
.wait()
|
|
||||||
.expect("Merging failed");
|
|
||||||
index_writer.wait_merging_threads().unwrap();
|
index_writer.wait_merging_threads().unwrap();
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -1040,11 +1036,7 @@ mod tests {
|
|||||||
let segment_ids = index
|
let segment_ids = index
|
||||||
.searchable_segment_ids()
|
.searchable_segment_ids()
|
||||||
.expect("Searchable segments failed.");
|
.expect("Searchable segments failed.");
|
||||||
index_writer
|
block_on(index_writer.merge(&segment_ids)).expect("Merging failed");
|
||||||
.merge(&segment_ids)
|
|
||||||
.expect("Failed to initiate merge")
|
|
||||||
.wait()
|
|
||||||
.expect("Merging failed");
|
|
||||||
reader.reload().unwrap();
|
reader.reload().unwrap();
|
||||||
let searcher = reader.searcher();
|
let searcher = reader.searcher();
|
||||||
assert_eq!(searcher.segment_readers().len(), 1);
|
assert_eq!(searcher.segment_readers().len(), 1);
|
||||||
@@ -1139,11 +1131,7 @@ mod tests {
|
|||||||
let segment_ids = index
|
let segment_ids = index
|
||||||
.searchable_segment_ids()
|
.searchable_segment_ids()
|
||||||
.expect("Searchable segments failed.");
|
.expect("Searchable segments failed.");
|
||||||
index_writer
|
block_on(index_writer.merge(&segment_ids)).expect("Merging failed");
|
||||||
.merge(&segment_ids)
|
|
||||||
.expect("Failed to initiate merge")
|
|
||||||
.wait()
|
|
||||||
.expect("Merging failed");
|
|
||||||
reader.reload().unwrap();
|
reader.reload().unwrap();
|
||||||
|
|
||||||
let searcher = reader.searcher();
|
let searcher = reader.searcher();
|
||||||
@@ -1277,11 +1265,7 @@ mod tests {
|
|||||||
.searchable_segment_ids()
|
.searchable_segment_ids()
|
||||||
.expect("Searchable segments failed.");
|
.expect("Searchable segments failed.");
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
||||||
index_writer
|
block_on(index_writer.merge(&segment_ids)).expect("Merging failed");
|
||||||
.merge(&segment_ids)
|
|
||||||
.expect("Failed to initiate merge")
|
|
||||||
.wait()
|
|
||||||
.expect("Merging failed");
|
|
||||||
index_writer.wait_merging_threads().unwrap();
|
index_writer.wait_merging_threads().unwrap();
|
||||||
reader.reload().unwrap();
|
reader.reload().unwrap();
|
||||||
test_searcher(
|
test_searcher(
|
||||||
@@ -1336,11 +1320,7 @@ mod tests {
|
|||||||
let segment_ids = index
|
let segment_ids = index
|
||||||
.searchable_segment_ids()
|
.searchable_segment_ids()
|
||||||
.expect("Searchable segments failed.");
|
.expect("Searchable segments failed.");
|
||||||
index_writer
|
block_on(index_writer.merge(&segment_ids)).expect("Merging failed");
|
||||||
.merge(&segment_ids)
|
|
||||||
.expect("Failed to initiate merge")
|
|
||||||
.wait()
|
|
||||||
.expect("Merging failed");
|
|
||||||
reader.reload().unwrap();
|
reader.reload().unwrap();
|
||||||
// commit has not been called yet. The document should still be
|
// commit has not been called yet. The document should still be
|
||||||
// there.
|
// there.
|
||||||
@@ -1361,22 +1341,18 @@ mod tests {
|
|||||||
let mut doc = Document::default();
|
let mut doc = Document::default();
|
||||||
doc.add_u64(int_field, 1);
|
doc.add_u64(int_field, 1);
|
||||||
index_writer.add_document(doc.clone());
|
index_writer.add_document(doc.clone());
|
||||||
index_writer.commit().expect("commit failed");
|
assert!(index_writer.commit().is_ok());
|
||||||
index_writer.add_document(doc);
|
index_writer.add_document(doc);
|
||||||
index_writer.commit().expect("commit failed");
|
assert!(index_writer.commit().is_ok());
|
||||||
index_writer.delete_term(Term::from_field_u64(int_field, 1));
|
index_writer.delete_term(Term::from_field_u64(int_field, 1));
|
||||||
|
|
||||||
let segment_ids = index
|
let segment_ids = index
|
||||||
.searchable_segment_ids()
|
.searchable_segment_ids()
|
||||||
.expect("Searchable segments failed.");
|
.expect("Searchable segments failed.");
|
||||||
index_writer
|
assert!(block_on(index_writer.merge(&segment_ids)).is_ok());
|
||||||
.merge(&segment_ids)
|
|
||||||
.expect("Failed to initiate merge")
|
|
||||||
.wait()
|
|
||||||
.expect("Merging failed");
|
|
||||||
|
|
||||||
// assert delete has not been committed
|
// assert delete has not been committed
|
||||||
reader.reload().expect("failed to load searcher 1");
|
assert!(reader.reload().is_ok());
|
||||||
let searcher = reader.searcher();
|
let searcher = reader.searcher();
|
||||||
assert_eq!(searcher.num_docs(), 2);
|
assert_eq!(searcher.num_docs(), 2);
|
||||||
|
|
||||||
@@ -1415,12 +1391,12 @@ mod tests {
|
|||||||
index_doc(&mut index_writer, &[1, 5]);
|
index_doc(&mut index_writer, &[1, 5]);
|
||||||
index_doc(&mut index_writer, &[3]);
|
index_doc(&mut index_writer, &[3]);
|
||||||
index_doc(&mut index_writer, &[17]);
|
index_doc(&mut index_writer, &[17]);
|
||||||
index_writer.commit().expect("committed");
|
assert!(index_writer.commit().is_ok());
|
||||||
index_doc(&mut index_writer, &[20]);
|
index_doc(&mut index_writer, &[20]);
|
||||||
index_writer.commit().expect("committed");
|
assert!(index_writer.commit().is_ok());
|
||||||
index_doc(&mut index_writer, &[28, 27]);
|
index_doc(&mut index_writer, &[28, 27]);
|
||||||
index_doc(&mut index_writer, &[1_000]);
|
index_doc(&mut index_writer, &[1_000]);
|
||||||
index_writer.commit().expect("committed");
|
assert!(index_writer.commit().is_ok());
|
||||||
}
|
}
|
||||||
let reader = index.reader().unwrap();
|
let reader = index.reader().unwrap();
|
||||||
let searcher = reader.searcher();
|
let searcher = reader.searcher();
|
||||||
@@ -1452,15 +1428,6 @@ mod tests {
|
|||||||
assert_eq!(&vals, &[17]);
|
assert_eq!(&vals, &[17]);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
|
||||||
"{:?}",
|
|
||||||
searcher
|
|
||||||
.segment_readers()
|
|
||||||
.iter()
|
|
||||||
.map(|reader| reader.max_doc())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let segment = searcher.segment_reader(1u32);
|
let segment = searcher.segment_reader(1u32);
|
||||||
let ff_reader = segment.fast_fields().u64s(int_field).unwrap();
|
let ff_reader = segment.fast_fields().u64s(int_field).unwrap();
|
||||||
@@ -1484,27 +1451,13 @@ mod tests {
|
|||||||
.searchable_segment_ids()
|
.searchable_segment_ids()
|
||||||
.expect("Searchable segments failed.");
|
.expect("Searchable segments failed.");
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
||||||
index_writer
|
assert!(block_on(index_writer.merge(&segment_ids)).is_ok());
|
||||||
.merge(&segment_ids)
|
assert!(index_writer.wait_merging_threads().is_ok());
|
||||||
.expect("Failed to initiate merge")
|
|
||||||
.wait()
|
|
||||||
.expect("Merging failed");
|
|
||||||
index_writer
|
|
||||||
.wait_merging_threads()
|
|
||||||
.expect("Wait for merging threads");
|
|
||||||
}
|
}
|
||||||
reader.reload().expect("Load searcher");
|
assert!(reader.reload().is_ok());
|
||||||
|
|
||||||
{
|
{
|
||||||
let searcher = reader.searcher();
|
let searcher = reader.searcher();
|
||||||
println!(
|
|
||||||
"{:?}",
|
|
||||||
searcher
|
|
||||||
.segment_readers()
|
|
||||||
.iter()
|
|
||||||
.map(|reader| reader.max_doc())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
let segment = searcher.segment_reader(0u32);
|
let segment = searcher.segment_reader(0u32);
|
||||||
let ff_reader = segment.fast_fields().u64s(int_field).unwrap();
|
let ff_reader = segment.fast_fields().u64s(int_field).unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ mod stamper;
|
|||||||
|
|
||||||
pub use self::index_writer::IndexWriter;
|
pub use self::index_writer::IndexWriter;
|
||||||
pub use self::log_merge_policy::LogMergePolicy;
|
pub use self::log_merge_policy::LogMergePolicy;
|
||||||
pub use self::merge_operation::{MergeOperation, MergeOperationInventory};
|
pub use self::merge_operation::MergeOperation;
|
||||||
pub use self::merge_policy::{MergeCandidate, MergePolicy, NoMergePolicy};
|
pub use self::merge_policy::{MergeCandidate, MergePolicy, NoMergePolicy};
|
||||||
pub use self::prepared_commit::PreparedCommit;
|
pub use self::prepared_commit::PreparedCommit;
|
||||||
pub use self::segment_entry::SegmentEntry;
|
pub use self::segment_entry::SegmentEntry;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use super::IndexWriter;
|
use super::IndexWriter;
|
||||||
use crate::Opstamp;
|
use crate::Opstamp;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
use futures::executor::block_on;
|
||||||
|
|
||||||
/// A prepared commit
|
/// A prepared commit
|
||||||
pub struct PreparedCommit<'a> {
|
pub struct PreparedCommit<'a> {
|
||||||
@@ -32,9 +33,11 @@ impl<'a> PreparedCommit<'a> {
|
|||||||
|
|
||||||
pub fn commit(self) -> Result<Opstamp> {
|
pub fn commit(self) -> Result<Opstamp> {
|
||||||
info!("committing {}", self.opstamp);
|
info!("committing {}", self.opstamp);
|
||||||
self.index_writer
|
let _ = block_on(
|
||||||
.segment_updater()
|
self.index_writer
|
||||||
.commit(self.opstamp, self.payload)?;
|
.segment_updater()
|
||||||
|
.schedule_commit(self.opstamp, self.payload),
|
||||||
|
);
|
||||||
Ok(self.opstamp)
|
Ok(self.opstamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,28 @@ struct SegmentRegisters {
|
|||||||
committed: SegmentRegister,
|
committed: SegmentRegister,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub(crate) enum SegmentsStatus {
|
||||||
|
Committed,
|
||||||
|
Uncommitted,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SegmentRegisters {
|
||||||
|
/// Check if all the segments are committed or uncommited.
|
||||||
|
///
|
||||||
|
/// If some segment is missing or segments are in a different state (this should not happen
|
||||||
|
/// if tantivy is used correctly), returns `None`.
|
||||||
|
fn segments_status(&self, segment_ids: &[SegmentId]) -> Option<SegmentsStatus> {
|
||||||
|
if self.uncommitted.contains_all(segment_ids) {
|
||||||
|
Some(SegmentsStatus::Uncommitted)
|
||||||
|
} else if self.committed.contains_all(segment_ids) {
|
||||||
|
Some(SegmentsStatus::Committed)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The segment manager stores the list of segments
|
/// The segment manager stores the list of segments
|
||||||
/// as well as their state.
|
/// as well as their state.
|
||||||
///
|
///
|
||||||
@@ -153,33 +175,35 @@ impl SegmentManager {
|
|||||||
let mut registers_lock = self.write();
|
let mut registers_lock = self.write();
|
||||||
registers_lock.uncommitted.add_segment_entry(segment_entry);
|
registers_lock.uncommitted.add_segment_entry(segment_entry);
|
||||||
}
|
}
|
||||||
|
// Replace a list of segments for their equivalent merged segment.
|
||||||
pub fn end_merge(
|
//
|
||||||
|
// Returns true if these segments are committed, false if the merge segments are uncommited.
|
||||||
|
pub(crate) fn end_merge(
|
||||||
&self,
|
&self,
|
||||||
before_merge_segment_ids: &[SegmentId],
|
before_merge_segment_ids: &[SegmentId],
|
||||||
after_merge_segment_entry: SegmentEntry,
|
after_merge_segment_entry: SegmentEntry,
|
||||||
) {
|
) -> crate::Result<SegmentsStatus> {
|
||||||
let mut registers_lock = self.write();
|
let mut registers_lock = self.write();
|
||||||
let target_register: &mut SegmentRegister = {
|
let segments_status = registers_lock
|
||||||
if registers_lock
|
.segments_status(before_merge_segment_ids)
|
||||||
.uncommitted
|
.ok_or_else(|| {
|
||||||
.contains_all(before_merge_segment_ids)
|
|
||||||
{
|
|
||||||
&mut registers_lock.uncommitted
|
|
||||||
} else if registers_lock
|
|
||||||
.committed
|
|
||||||
.contains_all(before_merge_segment_ids)
|
|
||||||
{
|
|
||||||
&mut registers_lock.committed
|
|
||||||
} else {
|
|
||||||
warn!("couldn't find segment in SegmentManager");
|
warn!("couldn't find segment in SegmentManager");
|
||||||
return;
|
crate::Error::InvalidArgument(
|
||||||
}
|
"The segments that were merged could not be found in the SegmentManager. \
|
||||||
|
This is not necessarily a bug, and can happen after a rollback for instance."
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let target_register: &mut SegmentRegister = match segments_status {
|
||||||
|
SegmentsStatus::Uncommitted => &mut registers_lock.uncommitted,
|
||||||
|
SegmentsStatus::Committed => &mut registers_lock.committed,
|
||||||
};
|
};
|
||||||
for segment_id in before_merge_segment_ids {
|
for segment_id in before_merge_segment_ids {
|
||||||
target_register.remove_segment(segment_id);
|
target_register.remove_segment(segment_id);
|
||||||
}
|
}
|
||||||
target_register.add_segment_entry(after_merge_segment_entry);
|
target_register.add_segment_entry(after_merge_segment_entry);
|
||||||
|
Ok(segments_status)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn committed_segment_metas(&self) -> Vec<SegmentMeta> {
|
pub fn committed_segment_metas(&self) -> Vec<SegmentMeta> {
|
||||||
|
|||||||
@@ -6,39 +6,34 @@ use crate::core::SegmentId;
|
|||||||
use crate::core::SegmentMeta;
|
use crate::core::SegmentMeta;
|
||||||
use crate::core::SerializableSegment;
|
use crate::core::SerializableSegment;
|
||||||
use crate::core::META_FILEPATH;
|
use crate::core::META_FILEPATH;
|
||||||
use crate::directory::{Directory, DirectoryClone};
|
use crate::directory::{Directory, DirectoryClone, GarbageCollectionResult};
|
||||||
use crate::error::TantivyError;
|
|
||||||
use crate::indexer::delete_queue::DeleteCursor;
|
use crate::indexer::delete_queue::DeleteCursor;
|
||||||
use crate::indexer::index_writer::advance_deletes;
|
use crate::indexer::index_writer::advance_deletes;
|
||||||
use crate::indexer::merge_operation::MergeOperationInventory;
|
use crate::indexer::merge_operation::MergeOperationInventory;
|
||||||
use crate::indexer::merger::IndexMerger;
|
use crate::indexer::merger::IndexMerger;
|
||||||
|
use crate::indexer::segment_manager::SegmentsStatus;
|
||||||
use crate::indexer::stamper::Stamper;
|
use crate::indexer::stamper::Stamper;
|
||||||
use crate::indexer::MergeOperation;
|
|
||||||
use crate::indexer::SegmentEntry;
|
use crate::indexer::SegmentEntry;
|
||||||
use crate::indexer::SegmentSerializer;
|
use crate::indexer::SegmentSerializer;
|
||||||
use crate::indexer::{DefaultMergePolicy, MergePolicy};
|
use crate::indexer::{DefaultMergePolicy, MergePolicy};
|
||||||
|
use crate::indexer::{MergeCandidate, MergeOperation};
|
||||||
use crate::schema::Schema;
|
use crate::schema::Schema;
|
||||||
use crate::Opstamp;
|
use crate::Opstamp;
|
||||||
use crate::Result;
|
use futures::channel::oneshot;
|
||||||
use futures::oneshot;
|
use futures::executor::{ThreadPool, ThreadPoolBuilder};
|
||||||
use futures::sync::oneshot::Receiver;
|
use futures::future::Future;
|
||||||
use futures::Future;
|
use futures::future::TryFutureExt;
|
||||||
use futures_cpupool::Builder as CpuPoolBuilder;
|
|
||||||
use futures_cpupool::CpuFuture;
|
|
||||||
use futures_cpupool::CpuPool;
|
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::mem;
|
use std::ops::Deref;
|
||||||
use std::ops::DerefMut;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use std::thread;
|
|
||||||
use std::thread::JoinHandle;
|
const NUM_MERGE_THREADS: usize = 4;
|
||||||
|
|
||||||
/// Save the index meta file.
|
/// Save the index meta file.
|
||||||
/// This operation is atomic :
|
/// This operation is atomic :
|
||||||
@@ -49,7 +44,7 @@ use std::thread::JoinHandle;
|
|||||||
/// and flushed.
|
/// and flushed.
|
||||||
///
|
///
|
||||||
/// This method is not part of tantivy's public API
|
/// This method is not part of tantivy's public API
|
||||||
pub fn save_new_metas(schema: Schema, directory: &mut dyn Directory) -> Result<()> {
|
pub fn save_new_metas(schema: Schema, directory: &mut dyn Directory) -> crate::Result<()> {
|
||||||
save_metas(
|
save_metas(
|
||||||
&IndexMeta {
|
&IndexMeta {
|
||||||
segments: Vec::new(),
|
segments: Vec::new(),
|
||||||
@@ -70,7 +65,7 @@ pub fn save_new_metas(schema: Schema, directory: &mut dyn Directory) -> Result<(
|
|||||||
/// and flushed.
|
/// and flushed.
|
||||||
///
|
///
|
||||||
/// This method is not part of tantivy's public API
|
/// This method is not part of tantivy's public API
|
||||||
fn save_metas(metas: &IndexMeta, directory: &mut dyn Directory) -> Result<()> {
|
fn save_metas(metas: &IndexMeta, directory: &mut dyn Directory) -> crate::Result<()> {
|
||||||
info!("save metas");
|
info!("save metas");
|
||||||
let mut buffer = serde_json::to_vec_pretty(metas)?;
|
let mut buffer = serde_json::to_vec_pretty(metas)?;
|
||||||
// Just adding a new line at the end of the buffer.
|
// Just adding a new line at the end of the buffer.
|
||||||
@@ -89,21 +84,38 @@ fn save_metas(metas: &IndexMeta, directory: &mut dyn Directory) -> Result<()> {
|
|||||||
// We voluntarily pass a merge_operation ref to guarantee that
|
// We voluntarily pass a merge_operation ref to guarantee that
|
||||||
// the merge_operation is alive during the process
|
// the merge_operation is alive during the process
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SegmentUpdater(Arc<InnerSegmentUpdater>);
|
pub(crate) struct SegmentUpdater(Arc<InnerSegmentUpdater>);
|
||||||
|
|
||||||
fn perform_merge(
|
impl Deref for SegmentUpdater {
|
||||||
merge_operation: &MergeOperation,
|
type Target = InnerSegmentUpdater;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn garbage_collect_files(
|
||||||
|
segment_updater: SegmentUpdater,
|
||||||
|
) -> crate::Result<GarbageCollectionResult> {
|
||||||
|
info!("Running garbage collection");
|
||||||
|
let mut index = segment_updater.index.clone();
|
||||||
|
index
|
||||||
|
.directory_mut()
|
||||||
|
.garbage_collect(move || segment_updater.list_files())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges a list of segments the list of segment givens in the `segment_entries`.
|
||||||
|
/// This function happens in the calling thread and is computationally expensive.
|
||||||
|
fn merge(
|
||||||
index: &Index,
|
index: &Index,
|
||||||
mut segment_entries: Vec<SegmentEntry>,
|
mut segment_entries: Vec<SegmentEntry>,
|
||||||
) -> Result<SegmentEntry> {
|
target_opstamp: Opstamp,
|
||||||
let target_opstamp = merge_operation.target_opstamp();
|
) -> crate::Result<SegmentEntry> {
|
||||||
|
|
||||||
// first we need to apply deletes to our segment.
|
// first we need to apply deletes to our segment.
|
||||||
let mut merged_segment = index.new_segment();
|
let mut merged_segment = index.new_segment();
|
||||||
|
|
||||||
// TODO add logging
|
// First we apply all of the delet to the merged segment, up to the target opstamp.
|
||||||
let schema = index.schema();
|
|
||||||
|
|
||||||
for segment_entry in &mut segment_entries {
|
for segment_entry in &mut segment_entries {
|
||||||
let segment = index.segment(segment_entry.meta().clone());
|
let segment = index.segment(segment_entry.meta().clone());
|
||||||
advance_deletes(segment, segment_entry, target_opstamp)?;
|
advance_deletes(segment, segment_entry, target_opstamp)?;
|
||||||
@@ -117,22 +129,19 @@ fn perform_merge(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// An IndexMerger is like a "view" of our merged segments.
|
// An IndexMerger is like a "view" of our merged segments.
|
||||||
let merger: IndexMerger = IndexMerger::open(schema, &segments[..])?;
|
let merger: IndexMerger = IndexMerger::open(index.schema(), &segments[..])?;
|
||||||
|
|
||||||
// ... we just serialize this index merger in our new segment
|
|
||||||
// to merge the two segments.
|
|
||||||
|
|
||||||
|
// ... we just serialize this index merger in our new segment to merge the two segments.
|
||||||
let segment_serializer = SegmentSerializer::for_segment(&mut merged_segment)?;
|
let segment_serializer = SegmentSerializer::for_segment(&mut merged_segment)?;
|
||||||
|
|
||||||
let num_docs = merger.write(segment_serializer)?;
|
let num_docs = merger.write(segment_serializer)?;
|
||||||
|
|
||||||
let segment_meta = index.new_segment_meta(merged_segment.id(), num_docs);
|
let segment_meta = index.new_segment_meta(merged_segment.id(), num_docs);
|
||||||
|
|
||||||
let after_merge_segment_entry = SegmentEntry::new(segment_meta.clone(), delete_cursor, None);
|
Ok(SegmentEntry::new(segment_meta.clone(), delete_cursor, None))
|
||||||
Ok(after_merge_segment_entry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InnerSegmentUpdater {
|
pub(crate) struct InnerSegmentUpdater {
|
||||||
// we keep a copy of the current active IndexMeta to
|
// we keep a copy of the current active IndexMeta to
|
||||||
// avoid loading the file everytime we need it in the
|
// avoid loading the file everytime we need it in the
|
||||||
// `SegmentUpdater`.
|
// `SegmentUpdater`.
|
||||||
@@ -140,12 +149,12 @@ struct InnerSegmentUpdater {
|
|||||||
// This should be up to date as all update happen through
|
// This should be up to date as all update happen through
|
||||||
// the unique active `SegmentUpdater`.
|
// the unique active `SegmentUpdater`.
|
||||||
active_metas: RwLock<Arc<IndexMeta>>,
|
active_metas: RwLock<Arc<IndexMeta>>,
|
||||||
pool: CpuPool,
|
pool: ThreadPool,
|
||||||
|
merge_thread_pool: ThreadPool,
|
||||||
|
|
||||||
index: Index,
|
index: Index,
|
||||||
segment_manager: SegmentManager,
|
segment_manager: SegmentManager,
|
||||||
merge_policy: RwLock<Arc<Box<dyn MergePolicy>>>,
|
merge_policy: RwLock<Arc<Box<dyn MergePolicy>>>,
|
||||||
merging_thread_id: AtomicUsize,
|
|
||||||
merging_threads: RwLock<HashMap<usize, JoinHandle<Result<()>>>>,
|
|
||||||
killed: AtomicBool,
|
killed: AtomicBool,
|
||||||
stamper: Stamper,
|
stamper: Stamper,
|
||||||
merge_operations: MergeOperationInventory,
|
merge_operations: MergeOperationInventory,
|
||||||
@@ -156,22 +165,31 @@ impl SegmentUpdater {
|
|||||||
index: Index,
|
index: Index,
|
||||||
stamper: Stamper,
|
stamper: Stamper,
|
||||||
delete_cursor: &DeleteCursor,
|
delete_cursor: &DeleteCursor,
|
||||||
) -> Result<SegmentUpdater> {
|
) -> crate::Result<SegmentUpdater> {
|
||||||
let segments = index.searchable_segment_metas()?;
|
let segments = index.searchable_segment_metas()?;
|
||||||
let segment_manager = SegmentManager::from_segments(segments, delete_cursor);
|
let segment_manager = SegmentManager::from_segments(segments, delete_cursor);
|
||||||
let pool = CpuPoolBuilder::new()
|
let pool = ThreadPoolBuilder::new()
|
||||||
.name_prefix("segment_updater")
|
.name_prefix("segment_updater")
|
||||||
.pool_size(1)
|
.pool_size(1)
|
||||||
.create();
|
.create()
|
||||||
|
.map_err(|_| {
|
||||||
|
crate::Error::SystemError("Failed to spawn segment updater thread".to_string())
|
||||||
|
})?;
|
||||||
|
let merge_thread_pool = ThreadPoolBuilder::new()
|
||||||
|
.name_prefix("merge_thread")
|
||||||
|
.pool_size(NUM_MERGE_THREADS)
|
||||||
|
.create()
|
||||||
|
.map_err(|_| {
|
||||||
|
crate::Error::SystemError("Failed to spawn segment merging thread".to_string())
|
||||||
|
})?;
|
||||||
let index_meta = index.load_metas()?;
|
let index_meta = index.load_metas()?;
|
||||||
Ok(SegmentUpdater(Arc::new(InnerSegmentUpdater {
|
Ok(SegmentUpdater(Arc::new(InnerSegmentUpdater {
|
||||||
active_metas: RwLock::new(Arc::new(index_meta)),
|
active_metas: RwLock::new(Arc::new(index_meta)),
|
||||||
pool,
|
pool,
|
||||||
|
merge_thread_pool,
|
||||||
index,
|
index,
|
||||||
segment_manager,
|
segment_manager,
|
||||||
merge_policy: RwLock::new(Arc::new(Box::new(DefaultMergePolicy::default()))),
|
merge_policy: RwLock::new(Arc::new(Box::new(DefaultMergePolicy::default()))),
|
||||||
merging_thread_id: AtomicUsize::default(),
|
|
||||||
merging_threads: RwLock::new(HashMap::new()),
|
|
||||||
killed: AtomicBool::new(false),
|
killed: AtomicBool::new(false),
|
||||||
stamper,
|
stamper,
|
||||||
merge_operations: Default::default(),
|
merge_operations: Default::default(),
|
||||||
@@ -179,65 +197,76 @@ impl SegmentUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_merge_policy(&self) -> Arc<Box<dyn MergePolicy>> {
|
pub fn get_merge_policy(&self) -> Arc<Box<dyn MergePolicy>> {
|
||||||
self.0.merge_policy.read().unwrap().clone()
|
self.merge_policy.read().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_merge_policy(&self, merge_policy: Box<dyn MergePolicy>) {
|
pub fn set_merge_policy(&self, merge_policy: Box<dyn MergePolicy>) {
|
||||||
let arc_merge_policy = Arc::new(merge_policy);
|
let arc_merge_policy = Arc::new(merge_policy);
|
||||||
*self.0.merge_policy.write().unwrap() = arc_merge_policy;
|
*self.merge_policy.write().unwrap() = arc_merge_policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_merging_thread_id(&self) -> usize {
|
fn schedule_future<T: 'static + Send, F: Future<Output = crate::Result<T>> + 'static + Send>(
|
||||||
self.0.merging_thread_id.fetch_add(1, Ordering::SeqCst)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_async<T: 'static + Send, F: 'static + Send + FnOnce(SegmentUpdater) -> T>(
|
|
||||||
&self,
|
&self,
|
||||||
f: F,
|
f: F,
|
||||||
) -> CpuFuture<T, TantivyError> {
|
) -> impl Future<Output = crate::Result<T>> {
|
||||||
let me_clone = self.clone();
|
let (sender, receiver) = oneshot::channel();
|
||||||
self.0.pool.spawn_fn(move || Ok(f(me_clone)))
|
self.pool.spawn_ok(async move {
|
||||||
|
let _ = sender.send(f.await);
|
||||||
|
});
|
||||||
|
receiver.unwrap_or_else(|_| {
|
||||||
|
let err_msg =
|
||||||
|
"A segment_updater future did not success. This should never happen.".to_string();
|
||||||
|
Err(crate::Error::SystemError(err_msg))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_segment(&self, segment_entry: SegmentEntry) {
|
pub fn schedule_add_segment(
|
||||||
self.run_async(|segment_updater| {
|
&self,
|
||||||
segment_updater.0.segment_manager.add_segment(segment_entry);
|
segment_entry: SegmentEntry,
|
||||||
segment_updater.consider_merge_options();
|
) -> impl Future<Output = crate::Result<()>> {
|
||||||
|
let segment_updater = self.clone();
|
||||||
|
self.schedule_future(async move {
|
||||||
|
segment_updater.segment_manager.add_segment(segment_entry);
|
||||||
|
segment_updater.consider_merge_options().await;
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
.forget();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Orders `SegmentManager` to remove all segments
|
/// Orders `SegmentManager` to remove all segments
|
||||||
pub(crate) fn remove_all_segments(&self) {
|
pub(crate) fn remove_all_segments(&self) {
|
||||||
self.0.segment_manager.remove_all_segments();
|
self.segment_manager.remove_all_segments();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kill(&mut self) {
|
pub fn kill(&mut self) {
|
||||||
self.0.killed.store(true, Ordering::Release);
|
self.killed.store(true, Ordering::Release);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_alive(&self) -> bool {
|
pub fn is_alive(&self) -> bool {
|
||||||
!self.0.killed.load(Ordering::Acquire)
|
!self.killed.load(Ordering::Acquire)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply deletes up to the target opstamp to all segments.
|
/// Apply deletes up to the target opstamp to all segments.
|
||||||
///
|
///
|
||||||
/// The method returns copies of the segment entries,
|
/// The method returns copies of the segment entries,
|
||||||
/// updated with the delete information.
|
/// updated with the delete information.
|
||||||
fn purge_deletes(&self, target_opstamp: Opstamp) -> Result<Vec<SegmentEntry>> {
|
fn purge_deletes(&self, target_opstamp: Opstamp) -> crate::Result<Vec<SegmentEntry>> {
|
||||||
let mut segment_entries = self.0.segment_manager.segment_entries();
|
let mut segment_entries = self.segment_manager.segment_entries();
|
||||||
for segment_entry in &mut segment_entries {
|
for segment_entry in &mut segment_entries {
|
||||||
let segment = self.0.index.segment(segment_entry.meta().clone());
|
let segment = self.index.segment(segment_entry.meta().clone());
|
||||||
advance_deletes(segment, segment_entry, target_opstamp)?;
|
advance_deletes(segment, segment_entry, target_opstamp)?;
|
||||||
}
|
}
|
||||||
Ok(segment_entries)
|
Ok(segment_entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_metas(&self, opstamp: Opstamp, commit_message: Option<String>) {
|
pub fn save_metas(
|
||||||
|
&self,
|
||||||
|
opstamp: Opstamp,
|
||||||
|
commit_message: Option<String>,
|
||||||
|
) -> crate::Result<()> {
|
||||||
if self.is_alive() {
|
if self.is_alive() {
|
||||||
let index = &self.0.index;
|
let index = &self.index;
|
||||||
let directory = index.directory();
|
let directory = index.directory();
|
||||||
let mut commited_segment_metas = self.0.segment_manager.committed_segment_metas();
|
let mut commited_segment_metas = self.segment_manager.committed_segment_metas();
|
||||||
|
|
||||||
// We sort segment_readers by number of documents.
|
// We sort segment_readers by number of documents.
|
||||||
// This is an heuristic to make multithreading more efficient.
|
// This is an heuristic to make multithreading more efficient.
|
||||||
@@ -259,16 +288,18 @@ impl SegmentUpdater {
|
|||||||
opstamp,
|
opstamp,
|
||||||
payload: commit_message,
|
payload: commit_message,
|
||||||
};
|
};
|
||||||
save_metas(&index_meta, directory.box_clone().borrow_mut())
|
// TODO add context to the error.
|
||||||
.expect("Could not save metas.");
|
save_metas(&index_meta, directory.box_clone().borrow_mut())?;
|
||||||
self.store_meta(&index_meta);
|
self.store_meta(&index_meta);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn garbage_collect_files(&self) -> CpuFuture<(), TantivyError> {
|
pub fn schedule_garbage_collect(
|
||||||
self.run_async(move |segment_updater| {
|
&self,
|
||||||
segment_updater.garbage_collect_files_exec();
|
) -> impl Future<Output = crate::Result<GarbageCollectionResult>> {
|
||||||
})
|
let garbage_collect_future = garbage_collect_files(self.clone());
|
||||||
|
self.schedule_future(garbage_collect_future)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List the files that are useful to the index.
|
/// List the files that are useful to the index.
|
||||||
@@ -276,148 +307,132 @@ impl SegmentUpdater {
|
|||||||
/// This does not include lock files, or files that are obsolete
|
/// This does not include lock files, or files that are obsolete
|
||||||
/// but have not yet been deleted by the garbage collector.
|
/// but have not yet been deleted by the garbage collector.
|
||||||
fn list_files(&self) -> HashSet<PathBuf> {
|
fn list_files(&self) -> HashSet<PathBuf> {
|
||||||
let mut files = HashSet::new();
|
let mut files: HashSet<PathBuf> = self
|
||||||
|
.index
|
||||||
|
.list_all_segment_metas()
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|segment_meta| segment_meta.list_files())
|
||||||
|
.collect();
|
||||||
files.insert(META_FILEPATH.to_path_buf());
|
files.insert(META_FILEPATH.to_path_buf());
|
||||||
for segment_meta in self.0.index.list_all_segment_metas() {
|
|
||||||
files.extend(segment_meta.list_files());
|
|
||||||
}
|
|
||||||
files
|
files
|
||||||
}
|
}
|
||||||
|
|
||||||
fn garbage_collect_files_exec(&self) {
|
pub fn schedule_commit(
|
||||||
info!("Running garbage collection");
|
&self,
|
||||||
let mut index = self.0.index.clone();
|
opstamp: Opstamp,
|
||||||
index.directory_mut().garbage_collect(|| self.list_files());
|
payload: Option<String>,
|
||||||
}
|
) -> impl Future<Output = crate::Result<()>> {
|
||||||
|
let segment_updater: SegmentUpdater = self.clone();
|
||||||
pub fn commit(&self, opstamp: Opstamp, payload: Option<String>) -> Result<()> {
|
self.schedule_future(async move {
|
||||||
self.run_async(move |segment_updater| {
|
|
||||||
if segment_updater.is_alive() {
|
if segment_updater.is_alive() {
|
||||||
let segment_entries = segment_updater
|
let segment_entries = segment_updater.purge_deletes(opstamp)?;
|
||||||
.purge_deletes(opstamp)
|
segment_updater.segment_manager.commit(segment_entries);
|
||||||
.expect("Failed purge deletes");
|
segment_updater.save_metas(opstamp, payload)?;
|
||||||
segment_updater.0.segment_manager.commit(segment_entries);
|
let _ = garbage_collect_files(segment_updater.clone()).await;
|
||||||
segment_updater.save_metas(opstamp, payload);
|
segment_updater.consider_merge_options().await;
|
||||||
segment_updater.garbage_collect_files_exec();
|
|
||||||
segment_updater.consider_merge_options();
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
.wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_merge(&self, segment_ids: &[SegmentId]) -> Result<Receiver<SegmentMeta>> {
|
|
||||||
let commit_opstamp = self.load_metas().opstamp;
|
|
||||||
let merge_operation = MergeOperation::new(
|
|
||||||
&self.0.merge_operations,
|
|
||||||
commit_opstamp,
|
|
||||||
segment_ids.to_vec(),
|
|
||||||
);
|
|
||||||
self.run_async(move |segment_updater| segment_updater.start_merge_impl(merge_operation))
|
|
||||||
.wait()?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_meta(&self, index_meta: &IndexMeta) {
|
fn store_meta(&self, index_meta: &IndexMeta) {
|
||||||
*self.0.active_metas.write().unwrap() = Arc::new(index_meta.clone());
|
*self.active_metas.write().unwrap() = Arc::new(index_meta.clone());
|
||||||
}
|
|
||||||
fn load_metas(&self) -> Arc<IndexMeta> {
|
|
||||||
self.0.active_metas.read().unwrap().clone()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_metas(&self) -> Arc<IndexMeta> {
|
||||||
|
self.active_metas.read().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn make_merge_operation(&self, segment_ids: &[SegmentId]) -> MergeOperation {
|
||||||
|
let commit_opstamp = self.load_metas().opstamp;
|
||||||
|
MergeOperation::new(&self.merge_operations, commit_opstamp, segment_ids.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts a merge operation. This function will block until the merge operation is effectively
|
||||||
|
// started. Note that it does not wait for the merge to terminate.
|
||||||
|
// The calling thread should not be block for a long time, as this only involve waiting for the
|
||||||
|
// `SegmentUpdater` queue which in turns only contains lightweight operations.
|
||||||
|
//
|
||||||
|
// The merge itself happens on a different thread.
|
||||||
|
//
|
||||||
|
// When successful, this function returns a `Future` for a `Result<SegmentMeta>` that represents
|
||||||
|
// the actual outcome of the merge operation.
|
||||||
|
//
|
||||||
|
// It returns an error if for some reason the merge operation could not be started.
|
||||||
|
//
|
||||||
|
// At this point an error is not necessarily the sign of a malfunction.
|
||||||
|
// (e.g. A rollback could have happened, between the instant when the merge operaiton was
|
||||||
|
// suggested and the moment when it ended up being executed.)
|
||||||
|
//
|
||||||
// `segment_ids` is required to be non-empty.
|
// `segment_ids` is required to be non-empty.
|
||||||
fn start_merge_impl(&self, merge_operation: MergeOperation) -> Result<Receiver<SegmentMeta>> {
|
pub fn start_merge(
|
||||||
|
&self,
|
||||||
|
merge_operation: MergeOperation,
|
||||||
|
) -> crate::Result<impl Future<Output = crate::Result<SegmentMeta>>> {
|
||||||
assert!(
|
assert!(
|
||||||
!merge_operation.segment_ids().is_empty(),
|
!merge_operation.segment_ids().is_empty(),
|
||||||
"Segment_ids cannot be empty."
|
"Segment_ids cannot be empty."
|
||||||
);
|
);
|
||||||
|
|
||||||
let segment_updater_clone = self.clone();
|
let segment_updater = self.clone();
|
||||||
let segment_entries: Vec<SegmentEntry> = self
|
let segment_entries: Vec<SegmentEntry> = self
|
||||||
.0
|
|
||||||
.segment_manager
|
.segment_manager
|
||||||
.start_merge(merge_operation.segment_ids())?;
|
.start_merge(merge_operation.segment_ids())?;
|
||||||
|
|
||||||
// let segment_ids_vec = merge_operation.segment_ids.to_vec();
|
info!("Starting merge - {:?}", merge_operation.segment_ids());
|
||||||
|
|
||||||
let merging_thread_id = self.get_merging_thread_id();
|
let (merging_future_send, merging_future_recv) =
|
||||||
info!(
|
oneshot::channel::<crate::Result<SegmentMeta>>();
|
||||||
"Starting merge thread #{} - {:?}",
|
|
||||||
merging_thread_id,
|
|
||||||
merge_operation.segment_ids()
|
|
||||||
);
|
|
||||||
let (merging_future_send, merging_future_recv) = oneshot();
|
|
||||||
|
|
||||||
// first we need to apply deletes to our segment.
|
self.merge_thread_pool.spawn_ok(async move {
|
||||||
let merging_join_handle = thread::Builder::new()
|
// The fact that `merge_operation` is moved here is important.
|
||||||
.name(format!("mergingthread-{}", merging_thread_id))
|
// Its lifetime is used to track how many merging thread are currently running,
|
||||||
.spawn(move || {
|
// as well as which segment is currently in merge and therefore should not be
|
||||||
// first we need to apply deletes to our segment.
|
// candidate for another merge.
|
||||||
let merge_result = perform_merge(
|
match merge(
|
||||||
&merge_operation,
|
&segment_updater.index,
|
||||||
&segment_updater_clone.0.index,
|
segment_entries,
|
||||||
segment_entries,
|
merge_operation.target_opstamp(),
|
||||||
);
|
) {
|
||||||
|
Ok(after_merge_segment_entry) => {
|
||||||
match merge_result {
|
let segment_meta = segment_updater
|
||||||
Ok(after_merge_segment_entry) => {
|
.end_merge(merge_operation, after_merge_segment_entry)
|
||||||
let merged_segment_meta = after_merge_segment_entry.meta().clone();
|
.await;
|
||||||
segment_updater_clone
|
let _send_result = merging_future_send.send(segment_meta);
|
||||||
.end_merge(merge_operation, after_merge_segment_entry)
|
}
|
||||||
.expect("Segment updater thread is corrupted.");
|
Err(e) => {
|
||||||
|
warn!(
|
||||||
// the future may fail if the listener of the oneshot future
|
"Merge of {:?} was cancelled: {:?}",
|
||||||
// has been destroyed.
|
merge_operation.segment_ids().to_vec(),
|
||||||
//
|
e
|
||||||
// This is not a problem here, so we just ignore any
|
);
|
||||||
// possible error.
|
// ... cancel merge
|
||||||
let _merging_future_res = merging_future_send.send(merged_segment_meta);
|
if cfg!(test) {
|
||||||
}
|
panic!("Merge failed.");
|
||||||
Err(e) => {
|
|
||||||
warn!(
|
|
||||||
"Merge of {:?} was cancelled: {:?}",
|
|
||||||
merge_operation.segment_ids(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
// ... cancel merge
|
|
||||||
if cfg!(test) {
|
|
||||||
panic!("Merge failed.");
|
|
||||||
}
|
|
||||||
// As `merge_operation` will be dropped, the segment in merge state will
|
|
||||||
// be available for merge again.
|
|
||||||
// `merging_future_send` will be dropped, sending an error to the future.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
segment_updater_clone
|
}
|
||||||
.0
|
});
|
||||||
.merging_threads
|
|
||||||
.write()
|
Ok(merging_future_recv
|
||||||
.unwrap()
|
.unwrap_or_else(|_| Err(crate::Error::SystemError("Merge failed".to_string()))))
|
||||||
.remove(&merging_thread_id);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.expect("Failed to spawn a thread.");
|
|
||||||
self.0
|
|
||||||
.merging_threads
|
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.insert(merging_thread_id, merging_join_handle);
|
|
||||||
Ok(merging_future_recv)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn consider_merge_options(&self) {
|
async fn consider_merge_options(&self) {
|
||||||
let merge_segment_ids: HashSet<SegmentId> = self.0.merge_operations.segment_in_merge();
|
let merge_segment_ids: HashSet<SegmentId> = self.merge_operations.segment_in_merge();
|
||||||
let (committed_segments, uncommitted_segments) =
|
let (committed_segments, uncommitted_segments) =
|
||||||
get_mergeable_segments(&merge_segment_ids, &self.0.segment_manager);
|
get_mergeable_segments(&merge_segment_ids, &self.segment_manager);
|
||||||
|
|
||||||
// Committed segments cannot be merged with uncommitted_segments.
|
// Committed segments cannot be merged with uncommitted_segments.
|
||||||
// We therefore consider merges using these two sets of segments independently.
|
// We therefore consider merges using these two sets of segments independently.
|
||||||
let merge_policy = self.get_merge_policy();
|
let merge_policy = self.get_merge_policy();
|
||||||
|
|
||||||
let current_opstamp = self.0.stamper.stamp();
|
let current_opstamp = self.stamper.stamp();
|
||||||
let mut merge_candidates: Vec<MergeOperation> = merge_policy
|
let mut merge_candidates: Vec<MergeOperation> = merge_policy
|
||||||
.compute_merge_candidates(&uncommitted_segments)
|
.compute_merge_candidates(&uncommitted_segments)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|merge_candidate| {
|
.map(|merge_candidate| {
|
||||||
MergeOperation::new(&self.0.merge_operations, current_opstamp, merge_candidate.0)
|
MergeOperation::new(&self.merge_operations, current_opstamp, merge_candidate.0)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -425,25 +440,18 @@ impl SegmentUpdater {
|
|||||||
let committed_merge_candidates = merge_policy
|
let committed_merge_candidates = merge_policy
|
||||||
.compute_merge_candidates(&committed_segments)
|
.compute_merge_candidates(&committed_segments)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|merge_candidate| {
|
.map(|merge_candidate: MergeCandidate| {
|
||||||
MergeOperation::new(&self.0.merge_operations, commit_opstamp, merge_candidate.0)
|
MergeOperation::new(&self.merge_operations, commit_opstamp, merge_candidate.0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
merge_candidates.extend(committed_merge_candidates.into_iter());
|
merge_candidates.extend(committed_merge_candidates.into_iter());
|
||||||
|
|
||||||
for merge_operation in merge_candidates {
|
for merge_operation in merge_candidates {
|
||||||
match self.start_merge_impl(merge_operation) {
|
if let Err(err) = self.start_merge(merge_operation) {
|
||||||
Ok(merge_future) => {
|
warn!(
|
||||||
if let Err(e) = merge_future.fuse().poll() {
|
"Starting the merge failed for the following reason. This is not fatal. {}",
|
||||||
error!("The merge task failed quickly after starting: {:?}", e);
|
err
|
||||||
}
|
);
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(
|
|
||||||
"Starting the merge failed for the following reason. This is not fatal. {}",
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -452,15 +460,17 @@ impl SegmentUpdater {
|
|||||||
&self,
|
&self,
|
||||||
merge_operation: MergeOperation,
|
merge_operation: MergeOperation,
|
||||||
mut after_merge_segment_entry: SegmentEntry,
|
mut after_merge_segment_entry: SegmentEntry,
|
||||||
) -> Result<()> {
|
) -> impl Future<Output = crate::Result<SegmentMeta>> {
|
||||||
self.run_async(move |segment_updater| {
|
let segment_updater = self.clone();
|
||||||
|
let after_merge_segment_meta = after_merge_segment_entry.meta().clone();
|
||||||
|
let end_merge_future = self.schedule_future(async move {
|
||||||
info!("End merge {:?}", after_merge_segment_entry.meta());
|
info!("End merge {:?}", after_merge_segment_entry.meta());
|
||||||
{
|
{
|
||||||
let mut delete_cursor = after_merge_segment_entry.delete_cursor().clone();
|
let mut delete_cursor = after_merge_segment_entry.delete_cursor().clone();
|
||||||
if let Some(delete_operation) = delete_cursor.get() {
|
if let Some(delete_operation) = delete_cursor.get() {
|
||||||
let committed_opstamp = segment_updater.load_metas().opstamp;
|
let committed_opstamp = segment_updater.load_metas().opstamp;
|
||||||
if delete_operation.opstamp < committed_opstamp {
|
if delete_operation.opstamp < committed_opstamp {
|
||||||
let index = &segment_updater.0.index;
|
let index = &segment_updater.index;
|
||||||
let segment = index.segment(after_merge_segment_entry.meta().clone());
|
let segment = index.segment(after_merge_segment_entry.meta().clone());
|
||||||
if let Err(e) = advance_deletes(
|
if let Err(e) = advance_deletes(
|
||||||
segment,
|
segment,
|
||||||
@@ -478,21 +488,26 @@ impl SegmentUpdater {
|
|||||||
// ... cancel merge
|
// ... cancel merge
|
||||||
// `merge_operations` are tracked. As it is dropped, the
|
// `merge_operations` are tracked. As it is dropped, the
|
||||||
// the segment_ids will be available again for merge.
|
// the segment_ids will be available again for merge.
|
||||||
return;
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let previous_metas = segment_updater.load_metas();
|
let previous_metas = segment_updater.load_metas();
|
||||||
segment_updater
|
let segments_status = segment_updater
|
||||||
.0
|
|
||||||
.segment_manager
|
.segment_manager
|
||||||
.end_merge(merge_operation.segment_ids(), after_merge_segment_entry);
|
.end_merge(merge_operation.segment_ids(), after_merge_segment_entry)?;
|
||||||
segment_updater.consider_merge_options();
|
|
||||||
segment_updater.save_metas(previous_metas.opstamp, previous_metas.payload.clone());
|
if segments_status == SegmentsStatus::Committed {
|
||||||
|
segment_updater
|
||||||
|
.save_metas(previous_metas.opstamp, previous_metas.payload.clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
segment_updater.consider_merge_options().await;
|
||||||
} // we drop all possible handle to a now useless `SegmentMeta`.
|
} // we drop all possible handle to a now useless `SegmentMeta`.
|
||||||
segment_updater.garbage_collect_files_exec();
|
let _ = garbage_collect_files(segment_updater).await;
|
||||||
})
|
Ok(())
|
||||||
.wait()
|
});
|
||||||
|
end_merge_future.map_ok(|_| after_merge_segment_meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for current merging threads.
|
/// Wait for current merging threads.
|
||||||
@@ -510,26 +525,13 @@ impl SegmentUpdater {
|
|||||||
///
|
///
|
||||||
/// Obsolete files will eventually be cleaned up
|
/// Obsolete files will eventually be cleaned up
|
||||||
/// by the directory garbage collector.
|
/// by the directory garbage collector.
|
||||||
pub fn wait_merging_thread(&self) -> Result<()> {
|
pub fn wait_merging_thread(&self) -> crate::Result<()> {
|
||||||
loop {
|
for merge_operation in self.merge_operations.changes_iter() {
|
||||||
let merging_threads: HashMap<usize, JoinHandle<Result<()>>> = {
|
if merge_operation.is_empty() {
|
||||||
let mut merging_threads = self.0.merging_threads.write().unwrap();
|
break;
|
||||||
mem::replace(merging_threads.deref_mut(), HashMap::new())
|
|
||||||
};
|
|
||||||
if merging_threads.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
debug!("wait merging thread {}", merging_threads.len());
|
|
||||||
for (_, merging_thread_handle) in merging_threads {
|
|
||||||
merging_thread_handle
|
|
||||||
.join()
|
|
||||||
.map(|_| ())
|
|
||||||
.map_err(|_| TantivyError::ErrorInThread("Merging thread failed.".into()))?;
|
|
||||||
}
|
|
||||||
// Our merging thread may have queued their completed merged segment.
|
|
||||||
// Let's wait for that too.
|
|
||||||
self.run_async(move |_| {}).wait()?;
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,7 +687,6 @@ mod tests {
|
|||||||
index_writer.segment_updater().remove_all_segments();
|
index_writer.segment_updater().remove_all_segments();
|
||||||
let seg_vec = index_writer
|
let seg_vec = index_writer
|
||||||
.segment_updater()
|
.segment_updater()
|
||||||
.0
|
|
||||||
.segment_manager
|
.segment_manager
|
||||||
.segment_entries();
|
.segment_entries();
|
||||||
assert!(seg_vec.is_empty());
|
assert!(seg_vec.is_empty());
|
||||||
|
|||||||
@@ -162,6 +162,11 @@ pub struct IndexReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IndexReader {
|
impl IndexReader {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn index(&self) -> Index {
|
||||||
|
self.inner.index.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Update searchers so that they reflect the state of the last
|
/// Update searchers so that they reflect the state of the last
|
||||||
/// `.commit()`.
|
/// `.commit()`.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::Pool;
|
use super::Pool;
|
||||||
use super::Queue;
|
use super::Queue;
|
||||||
use std::iter;
|
use std::{iter, mem};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pool() {
|
fn test_pool() {
|
||||||
@@ -197,33 +197,67 @@ mod tests {
|
|||||||
fn test_pool_dont_panic_on_empty_pop() {
|
fn test_pool_dont_panic_on_empty_pop() {
|
||||||
// When the object pool is exhausted, it shouldn't panic on pop()
|
// When the object pool is exhausted, it shouldn't panic on pop()
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{thread, time};
|
use std::thread;
|
||||||
|
|
||||||
// Wrap the pool in an Arc, same way as its used in `core/index.rs`
|
// Wrap the pool in an Arc, same way as its used in `core/index.rs`
|
||||||
let pool = Arc::new(Pool::new());
|
let pool1 = Arc::new(Pool::new());
|
||||||
// clone pools outside the move scope of each new thread
|
// clone pools outside the move scope of each new thread
|
||||||
let pool1 = Arc::clone(&pool);
|
let pool2 = Arc::clone(&pool1);
|
||||||
let pool2 = Arc::clone(&pool);
|
let pool3 = Arc::clone(&pool1);
|
||||||
|
|
||||||
let elements_for_pool = vec![1, 2];
|
let elements_for_pool = vec![1, 2];
|
||||||
pool.publish_new_generation(elements_for_pool);
|
pool1.publish_new_generation(elements_for_pool);
|
||||||
|
|
||||||
let mut threads = vec![];
|
let mut threads = vec![];
|
||||||
let sleep_dur = time::Duration::from_millis(10);
|
|
||||||
// spawn one more thread than there are elements in the pool
|
// spawn one more thread than there are elements in the pool
|
||||||
|
|
||||||
|
let (start_1_send, start_1_recv) = crossbeam::bounded(0);
|
||||||
|
let (start_2_send, start_2_recv) = crossbeam::bounded(0);
|
||||||
|
let (start_3_send, start_3_recv) = crossbeam::bounded(0);
|
||||||
|
|
||||||
|
let (event_send1, event_recv) = crossbeam::unbounded();
|
||||||
|
let event_send2 = event_send1.clone();
|
||||||
|
let event_send3 = event_send1.clone();
|
||||||
|
|
||||||
threads.push(thread::spawn(move || {
|
threads.push(thread::spawn(move || {
|
||||||
// leasing to make sure it's not dropped before sleep is called
|
assert_eq!(start_1_recv.recv(), Ok("start"));
|
||||||
let _leased_searcher = &pool.acquire();
|
|
||||||
thread::sleep(sleep_dur);
|
|
||||||
}));
|
|
||||||
threads.push(thread::spawn(move || {
|
|
||||||
// leasing to make sure it's not dropped before sleep is called
|
|
||||||
let _leased_searcher = &pool1.acquire();
|
let _leased_searcher = &pool1.acquire();
|
||||||
thread::sleep(sleep_dur);
|
assert!(event_send1.send("1 acquired").is_ok());
|
||||||
|
assert_eq!(start_1_recv.recv(), Ok("stop"));
|
||||||
|
assert!(event_send1.send("1 stopped").is_ok());
|
||||||
|
mem::drop(_leased_searcher);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
threads.push(thread::spawn(move || {
|
threads.push(thread::spawn(move || {
|
||||||
// leasing to make sure it's not dropped before sleep is called
|
assert_eq!(start_2_recv.recv(), Ok("start"));
|
||||||
let _leased_searcher = &pool2.acquire();
|
let _leased_searcher = &pool2.acquire();
|
||||||
thread::sleep(sleep_dur);
|
assert!(event_send2.send("2 acquired").is_ok());
|
||||||
|
assert_eq!(start_2_recv.recv(), Ok("stop"));
|
||||||
|
mem::drop(_leased_searcher);
|
||||||
|
assert!(event_send2.send("2 stopped").is_ok());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
threads.push(thread::spawn(move || {
|
||||||
|
assert_eq!(start_3_recv.recv(), Ok("start"));
|
||||||
|
let _leased_searcher = &pool3.acquire();
|
||||||
|
assert!(event_send3.send("3 acquired").is_ok());
|
||||||
|
assert_eq!(start_3_recv.recv(), Ok("stop"));
|
||||||
|
mem::drop(_leased_searcher);
|
||||||
|
assert!(event_send3.send("3 stopped").is_ok());
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert!(start_1_send.send("start").is_ok());
|
||||||
|
assert_eq!(event_recv.recv(), Ok("1 acquired"));
|
||||||
|
assert!(start_2_send.send("start").is_ok());
|
||||||
|
assert_eq!(event_recv.recv(), Ok("2 acquired"));
|
||||||
|
assert!(start_3_send.send("start").is_ok());
|
||||||
|
assert!(event_recv.try_recv().is_err());
|
||||||
|
assert!(start_1_send.send("stop").is_ok());
|
||||||
|
assert_eq!(event_recv.recv(), Ok("1 stopped"));
|
||||||
|
assert_eq!(event_recv.recv(), Ok("3 acquired"));
|
||||||
|
assert!(start_3_send.send("stop").is_ok());
|
||||||
|
assert_eq!(event_recv.recv(), Ok("3 stopped"));
|
||||||
|
assert!(start_2_send.send("stop").is_ok());
|
||||||
|
assert_eq!(event_recv.recv(), Ok("2 stopped"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,6 +233,12 @@ impl From<Vec<u8>> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<PreTokenizedString> for Value {
|
||||||
|
fn from(pretokenized_string: PreTokenizedString) -> Value {
|
||||||
|
Value::PreTokStr(pretokenized_string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod binary_serialize {
|
mod binary_serialize {
|
||||||
use super::Value;
|
use super::Value;
|
||||||
use crate::common::{f64_to_u64, u64_to_f64, BinarySerializable};
|
use crate::common::{f64_to_u64, u64_to_f64, BinarySerializable};
|
||||||
|
|||||||
@@ -331,9 +331,8 @@ mod tests {
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
|
|
||||||
const TEST_TEXT: &'static str =
|
const TEST_TEXT: &'static str = r#"Rust is a systems programming language sponsored by
|
||||||
r#"Rust is a systems programming language sponsored by Mozilla which
|
Mozilla which describes it as a "safe, concurrent, practical language", supporting functional and
|
||||||
describes it as a "safe, concurrent, practical language", supporting functional and
|
|
||||||
imperative-procedural paradigms. Rust is syntactically similar to C++[according to whom?],
|
imperative-procedural paradigms. Rust is syntactically similar to C++[according to whom?],
|
||||||
but its designers intend it to provide better memory safety while still maintaining
|
but its designers intend it to provide better memory safety while still maintaining
|
||||||
performance.
|
performance.
|
||||||
@@ -363,13 +362,13 @@ Survey in 2016, 2017, and 2018."#;
|
|||||||
let snippet = select_best_fragment_combination(&fragments[..], &TEST_TEXT);
|
let snippet = select_best_fragment_combination(&fragments[..], &TEST_TEXT);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
snippet.fragments,
|
snippet.fragments,
|
||||||
"Rust is a systems programming language sponsored by \
|
"Rust is a systems programming language sponsored by\n\
|
||||||
Mozilla which\ndescribes it as a \"safe"
|
Mozilla which describes it as a \"safe"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
snippet.to_html(),
|
snippet.to_html(),
|
||||||
"<b>Rust</b> is a systems programming <b>language</b> \
|
"<b>Rust</b> is a systems programming <b>language</b> \
|
||||||
sponsored by Mozilla which\ndescribes it as a "safe"
|
sponsored by\nMozilla which describes it as a "safe"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ fn test_failpoints_managed_directory_gc_if_delete_fails() {
|
|||||||
// The initial 1*off is there to allow for the removal of the
|
// The initial 1*off is there to allow for the removal of the
|
||||||
// lock file.
|
// lock file.
|
||||||
fail::cfg("RAMDirectory::delete", "1*off->1*return").unwrap();
|
fail::cfg("RAMDirectory::delete", "1*off->1*return").unwrap();
|
||||||
managed_directory.garbage_collect(Default::default);
|
assert!(managed_directory.garbage_collect(Default::default).is_ok());
|
||||||
assert!(managed_directory.exists(test_path));
|
assert!(managed_directory.exists(test_path));
|
||||||
|
|
||||||
// running the gc a second time should remove the file.
|
// running the gc a second time should remove the file.
|
||||||
managed_directory.garbage_collect(Default::default);
|
assert!(managed_directory.garbage_collect(Default::default).is_ok());
|
||||||
assert!(
|
assert!(
|
||||||
!managed_directory.exists(test_path),
|
!managed_directory.exists(test_path),
|
||||||
"The file should have been deleted"
|
"The file should have been deleted"
|
||||||
|
|||||||
Reference in New Issue
Block a user