1 // Copyright (C) 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "icing/file/posting_list/flash-index-storage.h"
16
17 #include <sys/types.h>
18 #include <unistd.h>
19
20 #include <algorithm>
21 #include <cerrno>
22 #include <cinttypes>
23 #include <cstdint>
24 #include <memory>
25
26 #include "icing/text_classifier/lib3/utils/base/status.h"
27 #include "icing/text_classifier/lib3/utils/base/statusor.h"
28 #include "icing/absl_ports/canonical_errors.h"
29 #include "icing/absl_ports/str_cat.h"
30 #include "icing/file/posting_list/index-block.h"
31 #include "icing/file/posting_list/posting-list-common.h"
32 #include "icing/legacy/core/icing-string-util.h"
33 #include "icing/util/logging.h"
34 #include "icing/util/math-util.h"
35 #include "icing/util/status-macros.h"
36
37 namespace icing {
38 namespace lib {
39
Create(std::string index_filename,const Filesystem * filesystem,PostingListSerializer * serializer,bool in_memory)40 libtextclassifier3::StatusOr<FlashIndexStorage> FlashIndexStorage::Create(
41 std::string index_filename, const Filesystem* filesystem,
42 PostingListSerializer* serializer, bool in_memory) {
43 ICING_RETURN_ERROR_IF_NULL(filesystem);
44 ICING_RETURN_ERROR_IF_NULL(serializer);
45
46 FlashIndexStorage storage(filesystem, std::move(index_filename), serializer,
47 in_memory);
48 if (!storage.Init()) {
49 return absl_ports::InternalError(
50 "Unable to successfully read header block!");
51 }
52 return storage;
53 }
54
55 /* static */ libtextclassifier3::StatusOr<int>
ReadHeaderMagic(const Filesystem * filesystem,const std::string & index_filename)56 FlashIndexStorage::ReadHeaderMagic(const Filesystem* filesystem,
57 const std::string& index_filename) {
58 ICING_RETURN_ERROR_IF_NULL(filesystem);
59
60 if (!filesystem->FileExists(index_filename.c_str())) {
61 return absl_ports::NotFoundError("Flash index file doesn't exist");
62 }
63
64 ScopedFd sfd(filesystem->OpenForRead(index_filename.c_str()));
65 if (!sfd.is_valid()) {
66 return absl_ports::InternalError("Fail to open flash index file");
67 }
68
69 uint32_t block_size = SelectBlockSize();
70 // Read and validate header.
71 ICING_ASSIGN_OR_RETURN(HeaderBlock header_block,
72 HeaderBlock::Read(filesystem, sfd.get(), block_size));
73 return header_block.header()->magic;
74 }
75
~FlashIndexStorage()76 FlashIndexStorage::~FlashIndexStorage() {
77 if (header_block_ != nullptr) {
78 FlushInMemoryFreeList();
79 PersistToDisk();
80 }
81 }
82
SelectBlockSize()83 /* static */ uint32_t FlashIndexStorage::SelectBlockSize() {
84 // This should be close to the flash page size.
85 static constexpr uint32_t kMinBlockSize = 4096;
86
87 // Determine a good block size.
88 uint32_t page_size = getpagesize();
89 uint32_t block_size = std::max(kMinBlockSize, page_size);
90
91 // Align up to the nearest page size.
92 return math_util::RoundUpTo(block_size, page_size);
93 }
94
Init()95 bool FlashIndexStorage::Init() {
96 storage_sfd_ = ScopedFd(filesystem_->OpenForWrite(index_filename_.c_str()));
97 if (!storage_sfd_.is_valid()) {
98 return false;
99 }
100
101 // Read in or create the header.
102 return InitHeader();
103 }
104
InitHeader()105 bool FlashIndexStorage::InitHeader() {
106 // Look for an existing file size.
107 int64_t file_size = filesystem_->GetFileSize(storage_sfd_.get());
108 if (file_size == Filesystem::kBadFileSize) {
109 ICING_LOG(ERROR) << "Could not initialize main index. Bad file size.";
110 return false;
111 }
112
113 if (file_size == 0) {
114 if (!CreateHeader()) {
115 ICING_LOG(ERROR)
116 << "Could not initialize main index. Unable to create header.";
117 return false;
118 }
119 } else {
120 if (!OpenHeader(file_size)) {
121 ICING_LOG(ERROR)
122 << "Could not initialize main index. Unable to open header.";
123 return false;
124 }
125 }
126 in_memory_freelists_.resize(header_block_->header()->num_index_block_infos);
127
128 return true;
129 }
130
CreateHeader()131 bool FlashIndexStorage::CreateHeader() {
132 uint32_t block_size = SelectBlockSize();
133 header_block_ = std::make_unique<HeaderBlock>(filesystem_, block_size);
134 // Initialize.
135 header_block_->header()->magic = HeaderBlock::Header::kMagic;
136 header_block_->header()->block_size = block_size;
137 header_block_->header()->last_indexed_docid = kInvalidDocumentId;
138
139 // Work down from the largest posting list that fits in
140 // block_size. We don't care about locality of blocks because this
141 // is a flash index.
142 for (uint32_t posting_list_bytes = max_posting_list_bytes();
143 posting_list_bytes >= serializer_->GetMinPostingListSize();
144 posting_list_bytes /= 2) {
145 uint32_t aligned_posting_list_bytes =
146 (posting_list_bytes / serializer_->GetDataTypeBytes()) *
147 serializer_->GetDataTypeBytes();
148 ICING_VLOG(1) << "Block size "
149 << header_block_->header()->num_index_block_infos << ": "
150 << aligned_posting_list_bytes;
151
152 // Initialize free list to empty.
153 HeaderBlock::Header::IndexBlockInfo* block_info =
154 header_block_->AddIndexBlockInfo();
155 if (block_info == nullptr) {
156 // This should never happen anyways. Min block size is 4k, so adding these
157 // IndexBlockInfos should never exceed the block size.
158 return false;
159 }
160 block_info->posting_list_bytes = aligned_posting_list_bytes;
161 block_info->free_list_block_index = kInvalidBlockIndex;
162 }
163
164 // Write the header.
165 if (!header_block_->Write(storage_sfd_.get())) {
166 filesystem_->Truncate(storage_sfd_.get(), 0);
167 return false;
168 }
169 num_blocks_ = 1;
170 return true;
171 }
172
OpenHeader(int64_t file_size)173 bool FlashIndexStorage::OpenHeader(int64_t file_size) {
174 uint32_t block_size = SelectBlockSize();
175 // Read and validate header.
176 ICING_ASSIGN_OR_RETURN(
177 HeaderBlock read_header,
178 HeaderBlock::Read(filesystem_, storage_sfd_.get(), block_size), false);
179 if (read_header.header()->magic != HeaderBlock::Header::kMagic) {
180 ICING_LOG(ERROR) << "Index header block wrong magic";
181 return false;
182 }
183 if (file_size % read_header.header()->block_size != 0) {
184 ICING_LOG(ERROR) << "Index size " << file_size
185 << " not a multiple of block size "
186 << read_header.header()->block_size;
187 return false;
188 }
189
190 if (file_size < static_cast<int64_t>(read_header.header()->block_size)) {
191 ICING_LOG(ERROR) << "Index size " << file_size
192 << " shorter than block size "
193 << read_header.header()->block_size;
194 return false;
195 }
196
197 if (read_header.header()->block_size % getpagesize() != 0) {
198 ICING_LOG(ERROR) << "Block size " << read_header.header()->block_size
199 << " is not a multiple of page size " << getpagesize();
200 return false;
201 }
202 num_blocks_ = file_size / read_header.header()->block_size;
203 if (block_size != read_header.header()->block_size) {
204 // The block_size changed? That's weird. But the old block_size is still
205 // valid (it must be some multiple of the new block_size). So reinitialize
206 // with that old block size. Using the old block size means that we can
207 // still use the main index, but reads/writes won't be as efficient in terms
208 // of flash IO because the 'blocks' that we're reading are actually multiple
209 // pages long.
210 ICING_LOG(ERROR) << "Block size of existing header ("
211 << read_header.header()->block_size
212 << ") does not match the requested block size ("
213 << block_size << "). Defaulting to existing block size "
214 << read_header.header()->block_size;
215 ICING_ASSIGN_OR_RETURN(HeaderBlock read_header,
216 HeaderBlock::Read(filesystem_, storage_sfd_.get(),
217 read_header.header()->block_size),
218 false);
219 }
220 header_block_ = std::make_unique<HeaderBlock>(std::move(read_header));
221
222 // Check for memory alignment on posting_list_bytes. See b/29983315.
223 // The issue of potential corruption to the header could also be handled by
224 // checksumming the header block.
225 for (int i = 0; i < header_block_->header()->num_index_block_infos; ++i) {
226 int posting_list_bytes =
227 header_block_->header()->index_block_infos[i].posting_list_bytes;
228 if (posting_list_bytes % serializer_->GetDataTypeBytes() != 0) {
229 ICING_LOG(ERROR)
230 << "Posting list size misaligned, index " << i << ", size "
231 << header_block_->header()->index_block_infos[i].posting_list_bytes
232 << ", data_type_bytes " << serializer_->GetDataTypeBytes()
233 << ", file_size " << file_size;
234 return false;
235 }
236 }
237 return true;
238 }
239
PersistToDisk()240 bool FlashIndexStorage::PersistToDisk() {
241 // First, write header.
242 if (!header_block_->Write(storage_sfd_.get())) {
243 ICING_LOG(ERROR) << "Write index header failed: " << strerror(errno);
244 return false;
245 }
246
247 // Then sync.
248 return filesystem_->DataSync(storage_sfd_.get());
249 }
250
Reset()251 libtextclassifier3::Status FlashIndexStorage::Reset() {
252 // Reset in-memory members to default values.
253 num_blocks_ = 0;
254 header_block_.reset();
255 storage_sfd_.reset();
256 in_memory_freelists_.clear();
257
258 // Delete the underlying file.
259 if (!filesystem_->DeleteFile(index_filename_.c_str())) {
260 return absl_ports::InternalError(
261 absl_ports::StrCat("Unable to delete file: ", index_filename_));
262 }
263
264 // Re-initialize.
265 if (!Init()) {
266 return absl_ports::InternalError(
267 "Unable to successfully read header block!");
268 }
269 return libtextclassifier3::Status::OK;
270 }
271
272 libtextclassifier3::StatusOr<PostingListHolder>
GetPostingList(PostingListIdentifier id) const273 FlashIndexStorage::GetPostingList(PostingListIdentifier id) const {
274 ICING_ASSIGN_OR_RETURN(IndexBlock block, GetIndexBlock(id.block_index()));
275 ICING_ASSIGN_OR_RETURN(
276 IndexBlock::PostingListAndBlockInfo pl_block_info,
277 block.GetAllocatedPostingList(id.posting_list_index()));
278 return PostingListHolder(std::move(pl_block_info.posting_list_used), id,
279 pl_block_info.next_block_index);
280 }
281
GetIndexBlock(uint32_t block_index) const282 libtextclassifier3::StatusOr<IndexBlock> FlashIndexStorage::GetIndexBlock(
283 uint32_t block_index) const {
284 if (block_index >= num_blocks_) {
285 return absl_ports::OutOfRangeError(IcingStringUtil::StringPrintf(
286 "Unable to create an index block at index %" PRIu32
287 " when only %d blocks have been allocated.",
288 block_index, num_blocks_));
289 }
290 off_t offset = static_cast<off_t>(block_index) * block_size();
291 return IndexBlock::CreateFromPreexistingIndexBlockRegion(
292 filesystem_, serializer_, storage_sfd_.get(), offset, block_size());
293 }
294
CreateIndexBlock(uint32_t block_index,uint32_t posting_list_size) const295 libtextclassifier3::StatusOr<IndexBlock> FlashIndexStorage::CreateIndexBlock(
296 uint32_t block_index, uint32_t posting_list_size) const {
297 if (block_index >= num_blocks_) {
298 return absl_ports::OutOfRangeError(IcingStringUtil::StringPrintf(
299 "Unable to create an index block at index %" PRIu32
300 " when only %d blocks have been allocated.",
301 block_index, num_blocks_));
302 }
303 off_t offset = static_cast<off_t>(block_index) * block_size();
304 return IndexBlock::CreateFromUninitializedRegion(
305 filesystem_, serializer_, storage_sfd_.get(), offset, block_size(),
306 posting_list_size);
307 }
308
FindBestIndexBlockInfo(uint32_t posting_list_bytes) const309 int FlashIndexStorage::FindBestIndexBlockInfo(
310 uint32_t posting_list_bytes) const {
311 int i = header_block_->header()->num_index_block_infos - 1;
312 for (; i >= 0; i--) {
313 if (header_block_->header()->index_block_infos[i].posting_list_bytes >=
314 posting_list_bytes) {
315 return i;
316 }
317 }
318 return i;
319 }
320
321 libtextclassifier3::StatusOr<PostingListHolder>
GetPostingListFromInMemoryFreeList(int block_info_index)322 FlashIndexStorage::GetPostingListFromInMemoryFreeList(int block_info_index) {
323 // Get something from in memory free list.
324 ICING_ASSIGN_OR_RETURN(PostingListIdentifier posting_list_id,
325 in_memory_freelists_[block_info_index].TryPop());
326 // Remember, posting lists stored on the in-memory free list were never
327 // actually freed. So it will still contain a valid PostingListUsed. First, we
328 // need to free this posting list.
329 ICING_ASSIGN_OR_RETURN(IndexBlock block,
330 GetIndexBlock(posting_list_id.block_index()));
331 ICING_RETURN_IF_ERROR(
332 block.FreePostingList(posting_list_id.posting_list_index()));
333
334 // Now, we can allocate a posting list from the same index block. It may not
335 // be the same posting list that was just freed, but that's okay.
336 ICING_ASSIGN_OR_RETURN(IndexBlock::PostingListAndBlockInfo pl_block_info,
337 block.AllocatePostingList());
338 posting_list_id = PostingListIdentifier(
339 posting_list_id.block_index(), pl_block_info.posting_list_index,
340 posting_list_id.posting_list_index_bits());
341
342 return PostingListHolder(std::move(pl_block_info.posting_list_used),
343 posting_list_id, pl_block_info.next_block_index);
344 }
345
346 libtextclassifier3::StatusOr<PostingListHolder>
GetPostingListFromOnDiskFreeList(int block_info_index)347 FlashIndexStorage::GetPostingListFromOnDiskFreeList(int block_info_index) {
348 // Get something from the free list.
349 uint32_t block_index = header_block_->header()
350 ->index_block_infos[block_info_index]
351 .free_list_block_index;
352 if (block_index == kInvalidBlockIndex) {
353 return absl_ports::NotFoundError("No available entry in free list.");
354 }
355
356 // Get the index block
357 ICING_ASSIGN_OR_RETURN(IndexBlock block, GetIndexBlock(block_index));
358 ICING_ASSIGN_OR_RETURN(IndexBlock::PostingListAndBlockInfo pl_block_info,
359 block.AllocatePostingList());
360 PostingListIdentifier posting_list_id =
361 PostingListIdentifier(block_index, pl_block_info.posting_list_index,
362 block.posting_list_index_bits());
363 if (!pl_block_info.has_free_posting_lists) {
364 ICING_RETURN_IF_ERROR(
365 RemoveFromOnDiskFreeList(block_index, block_info_index, &block));
366 }
367
368 return PostingListHolder(std::move(pl_block_info.posting_list_used),
369 posting_list_id, pl_block_info.next_block_index);
370 }
371
372 libtextclassifier3::StatusOr<PostingListHolder>
AllocateNewPostingList(int block_info_index)373 FlashIndexStorage::AllocateNewPostingList(int block_info_index) {
374 uint32_t block_index = GrowIndex();
375 if (block_index == kInvalidBlockIndex) {
376 return absl_ports::ResourceExhaustedError(
377 "Unable to grow the index further!");
378 }
379 ICING_ASSIGN_OR_RETURN(
380 IndexBlock block,
381 CreateIndexBlock(block_index, header_block_->header()
382 ->index_block_infos[block_info_index]
383 .posting_list_bytes));
384 ICING_ASSIGN_OR_RETURN(IndexBlock::PostingListAndBlockInfo pl_block_info,
385 block.AllocatePostingList());
386 PostingListIdentifier posting_list_id =
387 PostingListIdentifier(block_index, pl_block_info.posting_list_index,
388 block.posting_list_index_bits());
389 if (pl_block_info.has_free_posting_lists) {
390 AddToOnDiskFreeList(block_index, block_info_index, &block);
391 }
392
393 return PostingListHolder(std::move(pl_block_info.posting_list_used),
394 posting_list_id, pl_block_info.next_block_index);
395 }
396
397 libtextclassifier3::StatusOr<PostingListHolder>
AllocatePostingList(uint32_t min_posting_list_bytes)398 FlashIndexStorage::AllocatePostingList(uint32_t min_posting_list_bytes) {
399 int max_pl_size = max_posting_list_bytes();
400 if (min_posting_list_bytes > max_pl_size) {
401 return absl_ports::InvalidArgumentError(IcingStringUtil::StringPrintf(
402 "Requested posting list size %d exceeds max posting list size %d",
403 min_posting_list_bytes, max_pl_size));
404 }
405 int best_block_info_index = FindBestIndexBlockInfo(min_posting_list_bytes);
406
407 auto holder_or = GetPostingListFromInMemoryFreeList(best_block_info_index);
408 if (holder_or.ok()) {
409 return std::move(holder_or).ValueOrDie();
410 }
411
412 // Nothing in memory. Look for something in the block file.
413 holder_or = GetPostingListFromOnDiskFreeList(best_block_info_index);
414 if (holder_or.ok()) {
415 return std::move(holder_or).ValueOrDie();
416 }
417
418 return AllocateNewPostingList(best_block_info_index);
419 }
420
421 libtextclassifier3::StatusOr<PostingListHolder>
AllocateAndChainMaxSizePostingList(uint32_t prev_block_index)422 FlashIndexStorage::AllocateAndChainMaxSizePostingList(
423 uint32_t prev_block_index) {
424 uint32_t max_pl_size = max_posting_list_bytes();
425 int best_block_info_index = FindBestIndexBlockInfo(max_pl_size);
426
427 auto holder_or = GetPostingListFromInMemoryFreeList(best_block_info_index);
428 if (!holder_or.ok()) {
429 // Nothing in memory. Look for something in the block file.
430 holder_or = GetPostingListFromOnDiskFreeList(best_block_info_index);
431 }
432
433 if (!holder_or.ok()) {
434 // Nothing in memory or block file. Allocate new block and posting list.
435 holder_or = AllocateNewPostingList(best_block_info_index);
436 }
437
438 if (!holder_or.ok()) {
439 return holder_or;
440 }
441
442 PostingListHolder holder = std::move(holder_or).ValueOrDie();
443 ICING_ASSIGN_OR_RETURN(IndexBlock block,
444 GetIndexBlock(holder.id.block_index()));
445 ICING_RETURN_IF_ERROR(block.SetNextBlockIndex(prev_block_index));
446 holder.next_block_index = prev_block_index;
447 return holder;
448 }
449
AddToOnDiskFreeList(uint32_t block_index,int block_info_index,IndexBlock * index_block)450 void FlashIndexStorage::AddToOnDiskFreeList(uint32_t block_index,
451 int block_info_index,
452 IndexBlock* index_block) {
453 libtextclassifier3::Status status =
454 index_block->SetNextBlockIndex(header_block_->header()
455 ->index_block_infos[block_info_index]
456 .free_list_block_index);
457 if (!status.ok()) {
458 // If an error occurs, then simply skip this block. It just prevents us from
459 // allocating posting lists from this free block in the future and thus
460 // wastes at most one block, but the entire storage (including the
461 // FlashIndexStorage header) is still valid. Therefore, we can swallow
462 // errors here.
463 ICING_VLOG(1) << "Fail to set next block index to chain blocks with free "
464 "lists on disk: "
465 << status.error_message();
466 return;
467 }
468
469 header_block_->header()
470 ->index_block_infos[block_info_index]
471 .free_list_block_index = block_index;
472 }
473
RemoveFromOnDiskFreeList(uint32_t block_index,int block_info_index,IndexBlock * index_block)474 libtextclassifier3::Status FlashIndexStorage::RemoveFromOnDiskFreeList(
475 uint32_t block_index, int block_info_index, IndexBlock* index_block) {
476 // Cannot be used anymore. Move free ptr to the next block.
477 ICING_ASSIGN_OR_RETURN(uint32_t next_block_index,
478 index_block->GetNextBlockIndex());
479 ICING_RETURN_IF_ERROR(index_block->SetNextBlockIndex(kInvalidBlockIndex));
480 header_block_->header()
481 ->index_block_infos[block_info_index]
482 .free_list_block_index = next_block_index;
483 return libtextclassifier3::Status::OK;
484 }
485
FreePostingList(PostingListHolder && holder)486 libtextclassifier3::Status FlashIndexStorage::FreePostingList(
487 PostingListHolder&& holder) {
488 ICING_ASSIGN_OR_RETURN(IndexBlock block,
489 GetIndexBlock(holder.id.block_index()));
490
491 uint32_t posting_list_bytes = block.posting_list_bytes();
492 int best_block_info_index = FindBestIndexBlockInfo(posting_list_bytes);
493
494 // It *should* be guaranteed elsewhere that FindBestIndexBlockInfo will not
495 // return a value in >= in_memory_freelists_, but check regardless. If it
496 // doesn't fit for some reason, then put it in the Header free list instead.
497 if (has_in_memory_freelists_ &&
498 best_block_info_index < in_memory_freelists_.size()) {
499 in_memory_freelists_[best_block_info_index].Push(holder.id);
500 } else {
501 ICING_ASSIGN_OR_RETURN(bool was_not_full, block.HasFreePostingLists());
502 ICING_RETURN_IF_ERROR(
503 block.FreePostingList(holder.id.posting_list_index()));
504 // If this block was not already full, then it is already in the free list.
505 if (!was_not_full) {
506 AddToOnDiskFreeList(holder.id.block_index(), best_block_info_index,
507 &block);
508 }
509 }
510 return libtextclassifier3::Status::OK;
511 }
512
WritePostingListToDisk(const PostingListHolder & holder)513 libtextclassifier3::Status FlashIndexStorage::WritePostingListToDisk(
514 const PostingListHolder& holder) {
515 ICING_ASSIGN_OR_RETURN(IndexBlock block,
516 GetIndexBlock(holder.id.block_index()));
517 return block.WritePostingListToDisk(holder.posting_list,
518 holder.id.posting_list_index());
519 }
520
GrowIndex()521 int FlashIndexStorage::GrowIndex() {
522 if (num_blocks_ >= kMaxBlockIndex) {
523 ICING_VLOG(1) << "Reached max block index " << kMaxBlockIndex;
524 return kInvalidBlockIndex;
525 }
526
527 // Grow the index file.
528 if (!filesystem_->Grow(
529 storage_sfd_.get(),
530 static_cast<uint64_t>(num_blocks_ + 1) * block_size())) {
531 ICING_VLOG(1) << "Error growing index file: " << strerror(errno);
532 return kInvalidBlockIndex;
533 }
534
535 return num_blocks_++;
536 }
537
FlushInMemoryFreeList()538 libtextclassifier3::Status FlashIndexStorage::FlushInMemoryFreeList() {
539 for (int i = 0; i < in_memory_freelists_.size(); ++i) {
540 FreeList& freelist = in_memory_freelists_.at(i);
541 auto freelist_elt_or = freelist.TryPop();
542 while (freelist_elt_or.ok()) {
543 PostingListIdentifier freelist_elt = freelist_elt_or.ValueOrDie();
544 // Remember, posting lists stored on the in-memory free list were never
545 // actually freed. So it will still contain a valid PostingListUsed.
546 // First, we need to free this posting list.
547 auto block_or = GetIndexBlock(freelist_elt.block_index());
548 if (!block_or.ok()) {
549 // Can't read the block. Nothing to do here. This posting list will have
550 // to leak. Just proceed to the next freelist element.
551 freelist_elt_or = freelist.TryPop();
552 continue;
553 }
554 IndexBlock block = std::move(block_or).ValueOrDie();
555 ICING_ASSIGN_OR_RETURN(bool was_not_full, block.HasFreePostingLists());
556 ICING_RETURN_IF_ERROR(
557 block.FreePostingList(freelist_elt.posting_list_index()));
558 // If this block was not already full, then it is already in the free
559 // list.
560 if (!was_not_full) {
561 AddToOnDiskFreeList(freelist_elt.block_index(), /*block_info_index=*/i,
562 &block);
563 }
564 freelist_elt_or = freelist.TryPop();
565 }
566 }
567 return libtextclassifier3::Status::OK;
568 }
569
GetDebugInfo(DebugInfoVerbosity::Code verbosity,std::string * out) const570 void FlashIndexStorage::GetDebugInfo(DebugInfoVerbosity::Code verbosity,
571 std::string* out) const {
572 // Dump and check integrity of the index block free lists.
573 out->append("Free lists:\n");
574 for (size_t i = 0; i < header_block_->header()->num_index_block_infos; ++i) {
575 // TODO(tjbarron) Port over StringAppendFormat to migrate off of this legacy
576 // util.
577 IcingStringUtil::SStringAppendF(
578 out, 100, "Posting list bytes %u: ",
579 header_block_->header()->index_block_infos[i].posting_list_bytes);
580 uint32_t block_index =
581 header_block_->header()->index_block_infos[i].free_list_block_index;
582 int count = 0;
583 while (block_index != kInvalidBlockIndex) {
584 auto block_or = GetIndexBlock(block_index);
585 IcingStringUtil::SStringAppendF(out, 100, "%u ", block_index);
586 ++count;
587
588 block_index = kInvalidBlockIndex;
589 if (block_or.ok()) {
590 auto block_index_or = block_or.ValueOrDie().GetNextBlockIndex();
591 if (block_index_or.ok()) {
592 block_index = block_index_or.ValueOrDie();
593 }
594 }
595 }
596 IcingStringUtil::SStringAppendF(out, 100, "(count=%d)\n", count);
597 }
598
599 out->append("In memory free lists:\n");
600 if (in_memory_freelists_.size() ==
601 header_block_->header()->num_index_block_infos) {
602 for (size_t i = 0; i < in_memory_freelists_.size(); ++i) {
603 IcingStringUtil::SStringAppendF(
604 out, 100, "Posting list bytes %u %s\n",
605 header_block_->header()->index_block_infos[i].posting_list_bytes,
606 in_memory_freelists_.at(i).DebugString().c_str());
607 }
608 } else {
609 IcingStringUtil::SStringAppendF(
610 out, 100,
611 "In memory free list size %zu doesn't match index block infos size "
612 "%d\n",
613 in_memory_freelists_.size(),
614 header_block_->header()->num_index_block_infos);
615 }
616 }
617
618 // FreeList.
Push(PostingListIdentifier id)619 void FlashIndexStorage::FreeList::Push(PostingListIdentifier id) {
620 if (free_list_.size() >= kMaxSize) {
621 ICING_LOG(WARNING)
622 << "Freelist for posting lists of size (block_size / "
623 << (1u << id.posting_list_index_bits())
624 << ") has reached max size. Dropping freed posting list [block_index:"
625 << id.block_index()
626 << ", posting_list_index:" << id.posting_list_index() << "]";
627 ++num_dropped_free_list_entries_;
628 return;
629 }
630
631 free_list_.push_back(id);
632 free_list_size_high_watermark_ = std::max(
633 free_list_size_high_watermark_, static_cast<int>(free_list_.size()));
634 }
635
636 libtextclassifier3::StatusOr<PostingListIdentifier>
TryPop()637 FlashIndexStorage::FreeList::TryPop() {
638 if (free_list_.empty()) {
639 return absl_ports::NotFoundError("No available entry in free list.");
640 }
641
642 PostingListIdentifier id = free_list_.back();
643 free_list_.pop_back();
644 return id;
645 }
646
DebugString() const647 std::string FlashIndexStorage::FreeList::DebugString() const {
648 return IcingStringUtil::StringPrintf(
649 "size %zu max %d dropped %d", free_list_.size(),
650 free_list_size_high_watermark_, num_dropped_free_list_entries_);
651 }
652
653 } // namespace lib
654 } // namespace icing
655