Avoid recursion and use a simple loop to merge entries (#8239)

This avoids stack overflows when there are
lot of entries to be skipped, this PR also
optimizes the code to reuse the buffers.
master
Harshavardhana 5 years ago committed by kannappanr
parent fa32c71a56
commit 5392eee250
  1. 115
      cmd/xl-sets.go

@ -821,13 +821,16 @@ func (f *FileInfoCh) Push(fi FileInfo) {
f.Valid = true f.Valid = true
} }
// Calculate least entry across multiple FileInfo channels, additionally // Calculate least entry across multiple FileInfo channels,
// returns a boolean to indicate if the caller needs to call again. // returns the least common entry and the total number of times
func leastEntry(entriesCh []FileInfoCh, totalDrives int, heal bool) (FileInfo, bool) { // we found this entry. Additionally also returns a boolean
var entriesValid = make([]bool, len(entriesCh)) // to indicate if the caller needs to call this function
var entries = make([]FileInfo, len(entriesCh)) // again to list the next entry. It is callers responsibility
for i := range entriesCh { // if the caller wishes to list N entries to call leastEntry
entries[i], entriesValid[i] = entriesCh[i].Pop() // N times until this boolean is 'false'.
func leastEntry(entryChs []FileInfoCh, entries []FileInfo, entriesValid []bool) (FileInfo, int, bool) {
for i := range entryChs {
entries[i], entriesValid[i] = entryChs[i].Pop()
} }
var isTruncated = false var isTruncated = false
@ -856,9 +859,9 @@ func leastEntry(entriesCh []FileInfoCh, totalDrives int, heal bool) (FileInfo, b
} }
// We haven't been able to find any least entry, // We haven't been able to find any least entry,
// this would mean that we don't have valid. // this would mean that we don't have valid entry.
if !found { if !found {
return lentry, isTruncated return lentry, 0, isTruncated
} }
leastEntryCount := 0 leastEntryCount := 0
@ -876,53 +879,76 @@ func leastEntry(entriesCh []FileInfoCh, totalDrives int, heal bool) (FileInfo, b
// Push all entries which are lexically higher // Push all entries which are lexically higher
// and will be returned later in Pop() // and will be returned later in Pop()
entriesCh[i].Push(entries[i]) entryChs[i].Push(entries[i])
} }
quorum := lentry.Quorum return lentry, leastEntryCount, isTruncated
if quorum == 0 {
quorum = totalDrives / 2
}
if heal {
// When healing is enabled, we should
// list only objects which need healing.
if leastEntryCount != totalDrives {
return lentry, isTruncated
}
} else {
if leastEntryCount >= quorum {
return lentry, isTruncated
}
}
return leastEntry(entriesCh, totalDrives, heal)
} }
// mergeEntriesCh - merges FileInfo channel to entries upto maxKeys. // mergeEntriesCh - merges FileInfo channel to entries upto maxKeys.
func mergeEntriesCh(entriesCh []FileInfoCh, maxKeys int, totalDrives int, heal bool) (entries FilesInfo) { func mergeEntriesCh(entryChs []FileInfoCh, maxKeys int, totalDrives int, heal bool) (entries FilesInfo) {
var i = 0 var i = 0
entriesInfos := make([]FileInfo, len(entryChs))
entriesValid := make([]bool, len(entryChs))
for { for {
fi, valid := leastEntry(entriesCh, totalDrives, heal) fi, quorumCount, valid := leastEntry(entryChs, entriesInfos, entriesValid)
if !valid { if !valid {
// We have reached EOF across all entryChs, break the loop.
break break
} }
if i == maxKeys {
entries.IsTruncated = true rquorum := fi.Quorum
// Re-insert the last entry so it can be // Quorum is zero for all directories.
// listed in the next listing iteration. if rquorum == 0 {
for j := range entriesCh { // Choose N/2 quoroum for directory entries.
if !entriesCh[j].Valid { rquorum = totalDrives / 2
entriesCh[j].Push(fi) }
}
if heal {
// When healing is enabled, we should
// list only objects which need healing.
if quorumCount == totalDrives {
// Skip good entries.
continue
}
} else {
// Regular listing, we skip entries not in quorum.
if quorumCount < rquorum {
// Skip entries which do not have quorum.
continue
} }
break
} }
entries.Files = append(entries.Files, fi) entries.Files = append(entries.Files, fi)
i++ i++
if i == maxKeys {
entries.IsTruncated = isTruncated(entryChs, entriesInfos, entriesValid)
break
}
} }
return entries return entries
} }
func isTruncated(entryChs []FileInfoCh, entries []FileInfo, entriesValid []bool) bool {
for i := range entryChs {
entries[i], entriesValid[i] = entryChs[i].Pop()
}
var isTruncated = false
for _, valid := range entriesValid {
if !valid {
continue
}
isTruncated = true
break
}
for i := range entryChs {
if entriesValid[i] {
entryChs[i].Push(entries[i])
}
}
return isTruncated
}
// Starts a walk channel across all disks and returns a slice. // Starts a walk channel across all disks and returns a slice.
func (s *xlSets) startMergeWalks(ctx context.Context, bucket, prefix, marker string, recursive bool, endWalkCh chan struct{}) []FileInfoCh { func (s *xlSets) startMergeWalks(ctx context.Context, bucket, prefix, marker string, recursive bool, endWalkCh chan struct{}) []FileInfoCh {
var entryChs []FileInfoCh var entryChs []FileInfoCh
@ -955,15 +981,26 @@ func (s *xlSets) listObjectsNonSlash(ctx context.Context, bucket, prefix, marker
var eof bool var eof bool
var prevPrefix string var prevPrefix string
entriesValid := make([]bool, len(entryChs))
entries := make([]FileInfo, len(entryChs))
for { for {
if len(objInfos) == maxKeys { if len(objInfos) == maxKeys {
break break
} }
result, ok := leastEntry(entryChs, s.drivesPerSet, false) result, quorumCount, ok := leastEntry(entryChs, entries, entriesValid)
if !ok { if !ok {
eof = true eof = true
break break
} }
rquorum := result.Quorum
// Quorum is zero for all directories.
if rquorum == 0 {
// Choose N/2 quorum for directory entries.
rquorum = s.drivesPerSet / 2
}
if quorumCount < rquorum {
continue
}
var objInfo ObjectInfo var objInfo ObjectInfo

Loading…
Cancel
Save