You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

scorch.go 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. // Copyright (c) 2018 Couchbase, Inc.
  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. package scorch
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "sync"
  21. "sync/atomic"
  22. "time"
  23. "github.com/RoaringBitmap/roaring"
  24. "github.com/blevesearch/bleve/analysis"
  25. "github.com/blevesearch/bleve/document"
  26. "github.com/blevesearch/bleve/index"
  27. "github.com/blevesearch/bleve/index/scorch/segment"
  28. "github.com/blevesearch/bleve/index/store"
  29. "github.com/blevesearch/bleve/registry"
  30. bolt "go.etcd.io/bbolt"
  31. )
  32. const Name = "scorch"
  33. const Version uint8 = 2
  34. var ErrClosed = fmt.Errorf("scorch closed")
  35. type Scorch struct {
  36. nextSegmentID uint64
  37. stats Stats
  38. iStats internalStats
  39. readOnly bool
  40. version uint8
  41. config map[string]interface{}
  42. analysisQueue *index.AnalysisQueue
  43. path string
  44. unsafeBatch bool
  45. rootLock sync.RWMutex
  46. root *IndexSnapshot // holds 1 ref-count on the root
  47. rootPersisted []chan error // closed when root is persisted
  48. persistedCallbacks []index.BatchCallback
  49. nextSnapshotEpoch uint64
  50. eligibleForRemoval []uint64 // Index snapshot epochs that are safe to GC.
  51. ineligibleForRemoval map[string]bool // Filenames that should not be GC'ed yet.
  52. numSnapshotsToKeep int
  53. closeCh chan struct{}
  54. introductions chan *segmentIntroduction
  55. persists chan *persistIntroduction
  56. merges chan *segmentMerge
  57. introducerNotifier chan *epochWatcher
  58. persisterNotifier chan *epochWatcher
  59. rootBolt *bolt.DB
  60. asyncTasks sync.WaitGroup
  61. onEvent func(event Event)
  62. onAsyncError func(err error)
  63. pauseLock sync.RWMutex
  64. pauseCount uint64
  65. segPlugin segment.Plugin
  66. }
  67. type internalStats struct {
  68. persistEpoch uint64
  69. persistSnapshotSize uint64
  70. mergeEpoch uint64
  71. mergeSnapshotSize uint64
  72. newSegBufBytesAdded uint64
  73. newSegBufBytesRemoved uint64
  74. analysisBytesAdded uint64
  75. analysisBytesRemoved uint64
  76. }
  77. func NewScorch(storeName string,
  78. config map[string]interface{},
  79. analysisQueue *index.AnalysisQueue) (index.Index, error) {
  80. rv := &Scorch{
  81. version: Version,
  82. config: config,
  83. analysisQueue: analysisQueue,
  84. nextSnapshotEpoch: 1,
  85. closeCh: make(chan struct{}),
  86. ineligibleForRemoval: map[string]bool{},
  87. segPlugin: defaultSegmentPlugin,
  88. }
  89. // check if the caller has requested a specific segment type/version
  90. forcedSegmentVersion, ok := config["forceSegmentVersion"].(int)
  91. if ok {
  92. forcedSegmentType, ok2 := config["forceSegmentType"].(string)
  93. if !ok2 {
  94. return nil, fmt.Errorf(
  95. "forceSegmentVersion set to %d, must also specify forceSegmentType", forcedSegmentVersion)
  96. }
  97. err := rv.loadSegmentPlugin(forcedSegmentType,
  98. uint32(forcedSegmentVersion))
  99. if err != nil {
  100. return nil, err
  101. }
  102. }
  103. rv.root = &IndexSnapshot{parent: rv, refs: 1, creator: "NewScorch"}
  104. ro, ok := config["read_only"].(bool)
  105. if ok {
  106. rv.readOnly = ro
  107. }
  108. ub, ok := config["unsafe_batch"].(bool)
  109. if ok {
  110. rv.unsafeBatch = ub
  111. }
  112. ecbName, ok := config["eventCallbackName"].(string)
  113. if ok {
  114. rv.onEvent = RegistryEventCallbacks[ecbName]
  115. }
  116. aecbName, ok := config["asyncErrorCallbackName"].(string)
  117. if ok {
  118. rv.onAsyncError = RegistryAsyncErrorCallbacks[aecbName]
  119. }
  120. return rv, nil
  121. }
  122. func (s *Scorch) paused() uint64 {
  123. s.pauseLock.Lock()
  124. pc := s.pauseCount
  125. s.pauseLock.Unlock()
  126. return pc
  127. }
  128. func (s *Scorch) incrPause() {
  129. s.pauseLock.Lock()
  130. s.pauseCount++
  131. s.pauseLock.Unlock()
  132. }
  133. func (s *Scorch) decrPause() {
  134. s.pauseLock.Lock()
  135. s.pauseCount--
  136. s.pauseLock.Unlock()
  137. }
  138. func (s *Scorch) fireEvent(kind EventKind, dur time.Duration) {
  139. if s.onEvent != nil {
  140. s.incrPause()
  141. s.onEvent(Event{Kind: kind, Scorch: s, Duration: dur})
  142. s.decrPause()
  143. }
  144. }
  145. func (s *Scorch) fireAsyncError(err error) {
  146. if s.onAsyncError != nil {
  147. s.onAsyncError(err)
  148. }
  149. atomic.AddUint64(&s.stats.TotOnErrors, 1)
  150. }
  151. func (s *Scorch) Open() error {
  152. err := s.openBolt()
  153. if err != nil {
  154. return err
  155. }
  156. s.asyncTasks.Add(1)
  157. go s.mainLoop()
  158. if !s.readOnly && s.path != "" {
  159. s.asyncTasks.Add(1)
  160. go s.persisterLoop()
  161. s.asyncTasks.Add(1)
  162. go s.mergerLoop()
  163. }
  164. return nil
  165. }
  166. func (s *Scorch) openBolt() error {
  167. var ok bool
  168. s.path, ok = s.config["path"].(string)
  169. if !ok {
  170. return fmt.Errorf("must specify path")
  171. }
  172. if s.path == "" {
  173. s.unsafeBatch = true
  174. }
  175. var rootBoltOpt *bolt.Options
  176. if s.readOnly {
  177. rootBoltOpt = &bolt.Options{
  178. ReadOnly: true,
  179. }
  180. } else {
  181. if s.path != "" {
  182. err := os.MkdirAll(s.path, 0700)
  183. if err != nil {
  184. return err
  185. }
  186. }
  187. }
  188. rootBoltPath := s.path + string(os.PathSeparator) + "root.bolt"
  189. var err error
  190. if s.path != "" {
  191. s.rootBolt, err = bolt.Open(rootBoltPath, 0600, rootBoltOpt)
  192. if err != nil {
  193. return err
  194. }
  195. // now see if there is any existing state to load
  196. err = s.loadFromBolt()
  197. if err != nil {
  198. _ = s.Close()
  199. return err
  200. }
  201. }
  202. atomic.StoreUint64(&s.stats.TotFileSegmentsAtRoot, uint64(len(s.root.segment)))
  203. s.introductions = make(chan *segmentIntroduction)
  204. s.persists = make(chan *persistIntroduction)
  205. s.merges = make(chan *segmentMerge)
  206. s.introducerNotifier = make(chan *epochWatcher, 1)
  207. s.persisterNotifier = make(chan *epochWatcher, 1)
  208. s.closeCh = make(chan struct{})
  209. if !s.readOnly && s.path != "" {
  210. err := s.removeOldZapFiles() // Before persister or merger create any new files.
  211. if err != nil {
  212. _ = s.Close()
  213. return err
  214. }
  215. }
  216. s.numSnapshotsToKeep = NumSnapshotsToKeep
  217. if v, ok := s.config["numSnapshotsToKeep"]; ok {
  218. var t int
  219. if t, err = parseToInteger(v); err != nil {
  220. return fmt.Errorf("numSnapshotsToKeep parse err: %v", err)
  221. }
  222. if t > 0 {
  223. s.numSnapshotsToKeep = t
  224. }
  225. }
  226. return nil
  227. }
  228. func (s *Scorch) Close() (err error) {
  229. startTime := time.Now()
  230. defer func() {
  231. s.fireEvent(EventKindClose, time.Since(startTime))
  232. }()
  233. s.fireEvent(EventKindCloseStart, 0)
  234. // signal to async tasks we want to close
  235. close(s.closeCh)
  236. // wait for them to close
  237. s.asyncTasks.Wait()
  238. // now close the root bolt
  239. if s.rootBolt != nil {
  240. err = s.rootBolt.Close()
  241. s.rootLock.Lock()
  242. if s.root != nil {
  243. err2 := s.root.DecRef()
  244. if err == nil {
  245. err = err2
  246. }
  247. }
  248. s.root = nil
  249. s.rootLock.Unlock()
  250. }
  251. return
  252. }
  253. func (s *Scorch) Update(doc *document.Document) error {
  254. b := index.NewBatch()
  255. b.Update(doc)
  256. return s.Batch(b)
  257. }
  258. func (s *Scorch) Delete(id string) error {
  259. b := index.NewBatch()
  260. b.Delete(id)
  261. return s.Batch(b)
  262. }
  263. // Batch applices a batch of changes to the index atomically
  264. func (s *Scorch) Batch(batch *index.Batch) (err error) {
  265. start := time.Now()
  266. defer func() {
  267. s.fireEvent(EventKindBatchIntroduction, time.Since(start))
  268. }()
  269. resultChan := make(chan *index.AnalysisResult, len(batch.IndexOps))
  270. var numUpdates uint64
  271. var numDeletes uint64
  272. var numPlainTextBytes uint64
  273. var ids []string
  274. for docID, doc := range batch.IndexOps {
  275. if doc != nil {
  276. // insert _id field
  277. doc.AddField(document.NewTextFieldCustom("_id", nil, []byte(doc.ID), document.IndexField|document.StoreField, nil))
  278. numUpdates++
  279. numPlainTextBytes += doc.NumPlainTextBytes()
  280. } else {
  281. numDeletes++
  282. }
  283. ids = append(ids, docID)
  284. }
  285. // FIXME could sort ids list concurrent with analysis?
  286. if numUpdates > 0 {
  287. go func() {
  288. for _, doc := range batch.IndexOps {
  289. if doc != nil {
  290. aw := index.NewAnalysisWork(s, doc, resultChan)
  291. // put the work on the queue
  292. s.analysisQueue.Queue(aw)
  293. }
  294. }
  295. }()
  296. }
  297. // wait for analysis result
  298. analysisResults := make([]*index.AnalysisResult, int(numUpdates))
  299. var itemsDeQueued uint64
  300. var totalAnalysisSize int
  301. for itemsDeQueued < numUpdates {
  302. result := <-resultChan
  303. resultSize := result.Size()
  304. atomic.AddUint64(&s.iStats.analysisBytesAdded, uint64(resultSize))
  305. totalAnalysisSize += resultSize
  306. analysisResults[itemsDeQueued] = result
  307. itemsDeQueued++
  308. }
  309. close(resultChan)
  310. defer atomic.AddUint64(&s.iStats.analysisBytesRemoved, uint64(totalAnalysisSize))
  311. atomic.AddUint64(&s.stats.TotAnalysisTime, uint64(time.Since(start)))
  312. indexStart := time.Now()
  313. // notify handlers that we're about to introduce a segment
  314. s.fireEvent(EventKindBatchIntroductionStart, 0)
  315. var newSegment segment.Segment
  316. var bufBytes uint64
  317. if len(analysisResults) > 0 {
  318. newSegment, bufBytes, err = s.segPlugin.New(analysisResults)
  319. if err != nil {
  320. return err
  321. }
  322. atomic.AddUint64(&s.iStats.newSegBufBytesAdded, bufBytes)
  323. } else {
  324. atomic.AddUint64(&s.stats.TotBatchesEmpty, 1)
  325. }
  326. err = s.prepareSegment(newSegment, ids, batch.InternalOps, batch.PersistedCallback())
  327. if err != nil {
  328. if newSegment != nil {
  329. _ = newSegment.Close()
  330. }
  331. atomic.AddUint64(&s.stats.TotOnErrors, 1)
  332. } else {
  333. atomic.AddUint64(&s.stats.TotUpdates, numUpdates)
  334. atomic.AddUint64(&s.stats.TotDeletes, numDeletes)
  335. atomic.AddUint64(&s.stats.TotBatches, 1)
  336. atomic.AddUint64(&s.stats.TotIndexedPlainTextBytes, numPlainTextBytes)
  337. }
  338. atomic.AddUint64(&s.iStats.newSegBufBytesRemoved, bufBytes)
  339. atomic.AddUint64(&s.stats.TotIndexTime, uint64(time.Since(indexStart)))
  340. return err
  341. }
  342. func (s *Scorch) prepareSegment(newSegment segment.Segment, ids []string,
  343. internalOps map[string][]byte, persistedCallback index.BatchCallback) error {
  344. // new introduction
  345. introduction := &segmentIntroduction{
  346. id: atomic.AddUint64(&s.nextSegmentID, 1),
  347. data: newSegment,
  348. ids: ids,
  349. obsoletes: make(map[uint64]*roaring.Bitmap),
  350. internal: internalOps,
  351. applied: make(chan error),
  352. persistedCallback: persistedCallback,
  353. }
  354. if !s.unsafeBatch {
  355. introduction.persisted = make(chan error, 1)
  356. }
  357. // optimistically prepare obsoletes outside of rootLock
  358. s.rootLock.RLock()
  359. root := s.root
  360. root.AddRef()
  361. s.rootLock.RUnlock()
  362. defer func() { _ = root.DecRef() }()
  363. for _, seg := range root.segment {
  364. delta, err := seg.segment.DocNumbers(ids)
  365. if err != nil {
  366. return err
  367. }
  368. introduction.obsoletes[seg.id] = delta
  369. }
  370. introStartTime := time.Now()
  371. s.introductions <- introduction
  372. // block until this segment is applied
  373. err := <-introduction.applied
  374. if err != nil {
  375. return err
  376. }
  377. if introduction.persisted != nil {
  378. err = <-introduction.persisted
  379. }
  380. introTime := uint64(time.Since(introStartTime))
  381. atomic.AddUint64(&s.stats.TotBatchIntroTime, introTime)
  382. if atomic.LoadUint64(&s.stats.MaxBatchIntroTime) < introTime {
  383. atomic.StoreUint64(&s.stats.MaxBatchIntroTime, introTime)
  384. }
  385. return err
  386. }
  387. func (s *Scorch) SetInternal(key, val []byte) error {
  388. b := index.NewBatch()
  389. b.SetInternal(key, val)
  390. return s.Batch(b)
  391. }
  392. func (s *Scorch) DeleteInternal(key []byte) error {
  393. b := index.NewBatch()
  394. b.DeleteInternal(key)
  395. return s.Batch(b)
  396. }
  397. // Reader returns a low-level accessor on the index data. Close it to
  398. // release associated resources.
  399. func (s *Scorch) Reader() (index.IndexReader, error) {
  400. return s.currentSnapshot(), nil
  401. }
  402. func (s *Scorch) currentSnapshot() *IndexSnapshot {
  403. s.rootLock.RLock()
  404. rv := s.root
  405. if rv != nil {
  406. rv.AddRef()
  407. }
  408. s.rootLock.RUnlock()
  409. return rv
  410. }
  411. func (s *Scorch) Stats() json.Marshaler {
  412. return &s.stats
  413. }
  414. func (s *Scorch) diskFileStats(rootSegmentPaths map[string]struct{}) (uint64,
  415. uint64, uint64) {
  416. var numFilesOnDisk, numBytesUsedDisk, numBytesOnDiskByRoot uint64
  417. if s.path != "" {
  418. finfos, err := ioutil.ReadDir(s.path)
  419. if err == nil {
  420. for _, finfo := range finfos {
  421. if !finfo.IsDir() {
  422. numBytesUsedDisk += uint64(finfo.Size())
  423. numFilesOnDisk++
  424. if rootSegmentPaths != nil {
  425. fname := s.path + string(os.PathSeparator) + finfo.Name()
  426. if _, fileAtRoot := rootSegmentPaths[fname]; fileAtRoot {
  427. numBytesOnDiskByRoot += uint64(finfo.Size())
  428. }
  429. }
  430. }
  431. }
  432. }
  433. }
  434. // if no root files path given, then consider all disk files.
  435. if rootSegmentPaths == nil {
  436. return numFilesOnDisk, numBytesUsedDisk, numBytesUsedDisk
  437. }
  438. return numFilesOnDisk, numBytesUsedDisk, numBytesOnDiskByRoot
  439. }
  440. func (s *Scorch) rootDiskSegmentsPaths() map[string]struct{} {
  441. rv := make(map[string]struct{}, len(s.root.segment))
  442. for _, segmentSnapshot := range s.root.segment {
  443. if seg, ok := segmentSnapshot.segment.(segment.PersistedSegment); ok {
  444. rv[seg.Path()] = struct{}{}
  445. }
  446. }
  447. return rv
  448. }
  449. func (s *Scorch) StatsMap() map[string]interface{} {
  450. m := s.stats.ToMap()
  451. s.rootLock.RLock()
  452. rootSegPaths := s.rootDiskSegmentsPaths()
  453. m["CurFilesIneligibleForRemoval"] = uint64(len(s.ineligibleForRemoval))
  454. s.rootLock.RUnlock()
  455. numFilesOnDisk, numBytesUsedDisk, numBytesOnDiskByRoot := s.diskFileStats(rootSegPaths)
  456. m["CurOnDiskBytes"] = numBytesUsedDisk
  457. m["CurOnDiskFiles"] = numFilesOnDisk
  458. // TODO: consider one day removing these backwards compatible
  459. // names for apps using the old names
  460. m["updates"] = m["TotUpdates"]
  461. m["deletes"] = m["TotDeletes"]
  462. m["batches"] = m["TotBatches"]
  463. m["errors"] = m["TotOnErrors"]
  464. m["analysis_time"] = m["TotAnalysisTime"]
  465. m["index_time"] = m["TotIndexTime"]
  466. m["term_searchers_started"] = m["TotTermSearchersStarted"]
  467. m["term_searchers_finished"] = m["TotTermSearchersFinished"]
  468. m["num_plain_text_bytes_indexed"] = m["TotIndexedPlainTextBytes"]
  469. m["num_items_introduced"] = m["TotIntroducedItems"]
  470. m["num_items_persisted"] = m["TotPersistedItems"]
  471. m["num_recs_to_persist"] = m["TotItemsToPersist"]
  472. // total disk bytes found in index directory inclusive of older snapshots
  473. m["num_bytes_used_disk"] = numBytesUsedDisk
  474. // total disk bytes by the latest root index, exclusive of older snapshots
  475. m["num_bytes_used_disk_by_root"] = numBytesOnDiskByRoot
  476. m["num_files_on_disk"] = numFilesOnDisk
  477. m["num_root_memorysegments"] = m["TotMemorySegmentsAtRoot"]
  478. m["num_root_filesegments"] = m["TotFileSegmentsAtRoot"]
  479. m["num_persister_nap_pause_completed"] = m["TotPersisterNapPauseCompleted"]
  480. m["num_persister_nap_merger_break"] = m["TotPersisterMergerNapBreak"]
  481. m["total_compaction_written_bytes"] = m["TotFileMergeWrittenBytes"]
  482. return m
  483. }
  484. func (s *Scorch) Analyze(d *document.Document) *index.AnalysisResult {
  485. rv := &index.AnalysisResult{
  486. Document: d,
  487. Analyzed: make([]analysis.TokenFrequencies, len(d.Fields)+len(d.CompositeFields)),
  488. Length: make([]int, len(d.Fields)+len(d.CompositeFields)),
  489. }
  490. for i, field := range d.Fields {
  491. if field.Options().IsIndexed() {
  492. fieldLength, tokenFreqs := field.Analyze()
  493. rv.Analyzed[i] = tokenFreqs
  494. rv.Length[i] = fieldLength
  495. if len(d.CompositeFields) > 0 && field.Name() != "_id" {
  496. // see if any of the composite fields need this
  497. for _, compositeField := range d.CompositeFields {
  498. compositeField.Compose(field.Name(), fieldLength, tokenFreqs)
  499. }
  500. }
  501. }
  502. }
  503. return rv
  504. }
  505. func (s *Scorch) Advanced() (store.KVStore, error) {
  506. return nil, nil
  507. }
  508. func (s *Scorch) AddEligibleForRemoval(epoch uint64) {
  509. s.rootLock.Lock()
  510. if s.root == nil || s.root.epoch != epoch {
  511. s.eligibleForRemoval = append(s.eligibleForRemoval, epoch)
  512. }
  513. s.rootLock.Unlock()
  514. }
  515. func (s *Scorch) MemoryUsed() (memUsed uint64) {
  516. indexSnapshot := s.currentSnapshot()
  517. if indexSnapshot == nil {
  518. return
  519. }
  520. defer func() {
  521. _ = indexSnapshot.Close()
  522. }()
  523. // Account for current root snapshot overhead
  524. memUsed += uint64(indexSnapshot.Size())
  525. // Account for snapshot that the persister may be working on
  526. persistEpoch := atomic.LoadUint64(&s.iStats.persistEpoch)
  527. persistSnapshotSize := atomic.LoadUint64(&s.iStats.persistSnapshotSize)
  528. if persistEpoch != 0 && indexSnapshot.epoch > persistEpoch {
  529. // the snapshot that the persister is working on isn't the same as
  530. // the current snapshot
  531. memUsed += persistSnapshotSize
  532. }
  533. // Account for snapshot that the merger may be working on
  534. mergeEpoch := atomic.LoadUint64(&s.iStats.mergeEpoch)
  535. mergeSnapshotSize := atomic.LoadUint64(&s.iStats.mergeSnapshotSize)
  536. if mergeEpoch != 0 && indexSnapshot.epoch > mergeEpoch {
  537. // the snapshot that the merger is working on isn't the same as
  538. // the current snapshot
  539. memUsed += mergeSnapshotSize
  540. }
  541. memUsed += (atomic.LoadUint64(&s.iStats.newSegBufBytesAdded) -
  542. atomic.LoadUint64(&s.iStats.newSegBufBytesRemoved))
  543. memUsed += (atomic.LoadUint64(&s.iStats.analysisBytesAdded) -
  544. atomic.LoadUint64(&s.iStats.analysisBytesRemoved))
  545. return memUsed
  546. }
  547. func (s *Scorch) markIneligibleForRemoval(filename string) {
  548. s.rootLock.Lock()
  549. s.ineligibleForRemoval[filename] = true
  550. s.rootLock.Unlock()
  551. }
  552. func (s *Scorch) unmarkIneligibleForRemoval(filename string) {
  553. s.rootLock.Lock()
  554. delete(s.ineligibleForRemoval, filename)
  555. s.rootLock.Unlock()
  556. }
  557. func init() {
  558. registry.RegisterIndexType(Name, NewScorch)
  559. }
  560. func parseToInteger(i interface{}) (int, error) {
  561. switch v := i.(type) {
  562. case float64:
  563. return int(v), nil
  564. case int:
  565. return v, nil
  566. default:
  567. return 0, fmt.Errorf("expects int or float64 value")
  568. }
  569. }