Skip to content

[FME-8288] - Pausing - Reparsing #711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Split/Localhost/LocalhostSplitsStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ class LocalhostSplitsStorage: SplitsStorage {
func destroy() {
inMemorySplits.removeAll()
}

func forceParsing() {

}
}
17 changes: 15 additions & 2 deletions Split/Network/Sync/FeatureFlagsSynchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,26 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer {
return
}

let splitsStorage = self.storageContainer.splitsStorage
let splitsStorage = storageContainer.splitsStorage
let ruleBasedSegmentsStorage = storageContainer.ruleBasedSegmentsStorage
DispatchQueue.general.async {

DispatchQueue.general.async { [weak self] in
guard let self = self else { return }

let start = Date.nowMillis()
self.filterSplitsInCache()

// Part of /memberships hits optimization
if self.storageContainer.generalInfoStorage.getSegmentsInUse() == nil {
splitsStorage.forceParsing()
ruleBasedSegmentsStorage.forceParsing()
}

// Load local
splitsStorage.loadLocal()
ruleBasedSegmentsStorage.loadLocal()

// Events & Logs
if splitsStorage.getAll().count > 0 {
self.splitEventsManager.notifyInternalEvent(.splitsLoadedFromCache)
}
Expand Down
6 changes: 3 additions & 3 deletions Split/Storage/GeneralInfo/GeneralInfoStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ protocol GeneralInfoStorage {
func setLastProxyUpdateTimestamp(_ timestamp: Int64)

// Segments in use (for /memberships optimization)
func getSegmentsInUse() -> Int64
func getSegmentsInUse() -> Int64?
func setSegmentsInUse(_ count: Int64)
}

Expand Down Expand Up @@ -83,8 +83,8 @@ class DefaultGeneralInfoStorage: GeneralInfoStorage {
generalInfoDao.update(info: .lastProxyUpdateTimestamp, longValue: timestamp)
}

func getSegmentsInUse() -> Int64 {
return generalInfoDao.longValue(info: .segmentsInUse) ?? 0
func getSegmentsInUse() -> Int64? {
generalInfoDao.longValue(info: .segmentsInUse)
}

func setSegmentsInUse(_ count: Int64) {
Expand Down
2 changes: 1 addition & 1 deletion Split/Storage/MySegments/MyLargeSegmentsStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,6 @@ class MyLargeSegmentsStorage: MySegmentsStorage {

// MARK: For Network Traffic Optimization
func isUsingSegments() -> Bool {
generalInfoStorage.getSegmentsInUse() > 0
(generalInfoStorage.getSegmentsInUse() ?? 0) > 0
}
}
2 changes: 1 addition & 1 deletion Split/Storage/MySegments/MySegmentsStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,6 @@ class DefaultMySegmentsStorage: MySegmentsStorage {

// MARK: For Network Traffic Optimization
func isUsingSegments() -> Bool {
generalInfoStorage.getSegmentsInUse() > 0
(generalInfoStorage.getSegmentsInUse() ?? 0) > 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ protocol PersistentRuleBasedSegmentsStorage {
func clear()
func getChangeNumber() -> Int64

func getSegmentsInUse() -> Int64
func getSegmentsInUse() -> Int64?
func setSegmentsInUse(_ segmentsInUse: Int64)
}

Expand Down Expand Up @@ -59,7 +59,7 @@ class DefaultPersistentRuleBasedSegmentsStorage: PersistentRuleBasedSegmentsStor
return generalInfoStorage.getRuleBasedSegmentsChangeNumber()
}

func getSegmentsInUse() -> Int64 {
func getSegmentsInUse() -> Int64? {
generalInfoStorage.getSegmentsInUse()
}

Expand Down
122 changes: 71 additions & 51 deletions Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ protocol RuleBasedSegmentsStorage: RolloutDefinitionsCache {
func contains(segmentNames: Set<String>) -> Bool
func update(toAdd: Set<RuleBasedSegment>, toRemove: Set<RuleBasedSegment>, changeNumber: Int64) -> Bool
func loadLocal()
func forceParsing() // For Lazy Parsing optimization
}

class DefaultRuleBasedSegmentsStorage: RuleBasedSegmentsStorage {
Expand All @@ -33,45 +34,29 @@ class DefaultRuleBasedSegmentsStorage: RuleBasedSegmentsStorage {
}

func loadLocal() {
segmentsInUse = persistentStorage.getSegmentsInUse()
segmentsInUse = persistentStorage.getSegmentsInUse() ?? 0
let snapshot = persistentStorage.getSnapshot()
let active = snapshot.segments.filter { $0.status == .active }
let archived = snapshot.segments.filter { $0.status == .archived }

// Process active segments
for segment in active {
if let segmentName = segment.name?.lowercased() {
inMemorySegments.setValue(segment, forKey: segmentName)

if StorageHelper.usesSegments(segment.conditions) {
segmentsInUse += 1
}
}
}

// Process archived segments - remove them from memory if they exist
for segment in archived {
if let segmentName = segment.name?.lowercased() {
inMemorySegments.removeValue(forKey: segmentName)
}
}
_ = processToAdd(Set(active))
_ = processToRemove(Set(archived))

changeNumber = snapshot.changeNumber
persistentStorage.setSegmentsInUse(segmentsInUse)
}

func get(segmentName: String) -> RuleBasedSegment? {
guard let segment = inMemorySegments.value(forKey: segmentName.lowercased()) else {
return nil
}
guard let segment = inMemorySegments.value(forKey: segmentName.lowercased()) else { return nil }

if !segment.isParsed {
if let parsed = try? Json.decodeFrom(json: segment.json, to: RuleBasedSegment.self) {
inMemorySegments.setValue(parsed, forKey: segmentName.lowercased())
return parsed
if !segment.isParsed { // Parse if neccesaty (Lazy Parsing)
if let parsedSegment = parseSegment(segment) {
inMemorySegments.setValue(parsedSegment, forKey: segmentName.lowercased())
return parsedSegment
}
return nil
}

return segment
}

Expand All @@ -83,49 +68,84 @@ class DefaultRuleBasedSegmentsStorage: RuleBasedSegmentsStorage {

func update(toAdd: Set<RuleBasedSegment>, toRemove: Set<RuleBasedSegment>, changeNumber: Int64) -> Bool {

var updated = false
segmentsInUse = persistentStorage.getSegmentsInUse()
segmentsInUse = persistentStorage.getSegmentsInUse() ?? 0
self.changeNumber = changeNumber

// Keep count of Segments in use
for segment in toAdd.union(toRemove) {
if StorageHelper.usesSegments(segment.conditions) {
if let segmentName = segment.name?.lowercased(), segment.status == .active && inMemorySegments.value(forKey: segmentName) == nil {
segmentsInUse += 1
} else if inMemorySegments.value(forKey: segment.name?.lowercased() ?? "") != nil && segment.status != .active {
segmentsInUse -= 1
}
}
}
// Process
let addResult = processToAdd(toAdd)
let removeResult = processToRemove(toRemove)

// Update persistent storage
persistentStorage.update(toAdd: toAdd, toRemove: toRemove, changeNumber: changeNumber)
persistentStorage.setSegmentsInUse(segmentsInUse)

return addResult || removeResult
}

// Process segments to add
private func processToAdd(_ toAdd: Set<RuleBasedSegment>) -> Bool { // Process segments to add
var result = false

for segment in toAdd {
if let segmentName = segment.name?.lowercased() {
updateSegmentsCount(segment)
inMemorySegments.setValue(segment, forKey: segmentName)

updated = true
result = true
}
}

// Process segments to remove
return result
}

private func processToRemove(_ toRemove: Set<RuleBasedSegment>) -> Bool { // Process segments to remove
var result = false

for segment in toRemove {
if let segmentName = segment.name?.lowercased(), inMemorySegments.value(forKey: segmentName) != nil {
updateSegmentsCount(segment)
inMemorySegments.removeValue(forKey: segmentName)
updated = true
result = true
}
}

self.changeNumber = changeNumber

// Update persistent storage
persistentStorage.update(toAdd: toAdd, toRemove: toRemove, changeNumber: changeNumber)
persistentStorage.setSegmentsInUse(segmentsInUse)

return updated
return result
}

func clear() {
inMemorySegments.removeAll()
changeNumber = -1
persistentStorage.clear()
}

func forceParsing() {
segmentsInUse = persistentStorage.getSegmentsInUse() ?? 0
let activeSegments = persistentStorage.getSnapshot().segments.filter { $0.status == .active }

for i in 0..<activeSegments.count {
guard let segmentName = activeSegments[i].name else { continue }

if let parsedSegment = parseSegment(activeSegments[i]) { // Parse it
updateSegmentsCount(parsedSegment)
inMemorySegments.setValue(parsedSegment, forKey: segmentName)
}
}

persistentStorage.setSegmentsInUse(segmentsInUse)
}

fileprivate func parseSegment(_ segment: RuleBasedSegment) -> RuleBasedSegment? {
guard let parsedSegment = try? Json.decodeFrom(json: segment.json, to: RuleBasedSegment.self) else { return nil }
return parsedSegment
}

fileprivate func updateSegmentsCount(_ segment: RuleBasedSegment) {
if let segmentName = segment.name?.lowercased(), segment.status == .active, inMemorySegments.value(forKey: segmentName) == nil, StorageHelper.usesSegments(segment.conditions) {
segmentsInUse += 1
} else if inMemorySegments.value(forKey: segment.name?.lowercased() ?? "") != nil, segment.status != .active, StorageHelper.usesSegments(segment.conditions) {
segmentsInUse -= 1
}
}

#if DEBUG
func getInMemorySegments() -> ConcurrentDictionary<String, RuleBasedSegment> {
inMemorySegments
}
#endif
}
Loading
Loading