• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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