Further fixes

• Updated the pollers to NOT take the duration of the previous poll into account when scheduling the next poll
• Updated to the latest `libSession` version (a few optimisations/fixes)
• Fixed a minor layout issue with the attribution label on the Path screen
pull/986/head
Morgan Pretty 9 months ago
parent 6c9f4b506d
commit 65807ec6d3

@ -1 +1 @@
Subproject commit 2163461ae13ded4414da0f43aa876e0706504068
Subproject commit 449c86d645df7a82059693ee70178725ad00ffab

@ -44,6 +44,19 @@ final class PathVC: BaseVC {
return result
}()
private let attributionLabel: UILabel = {
let result = UILabel()
result.font = .systemFont(ofSize: Values.verySmallFontSize)
result.text = "Path location data provided by MaxMind"
result.themeTextColor = .textSecondary
result.textAlignment = .center
result.lineBreakMode = .byWordWrapping
result.numberOfLines = 0
result.isHidden = true
return result
}()
private lazy var learnMoreButton: SessionButton = {
let result = SessionButton(style: .bordered, size: .large)
@ -98,26 +111,12 @@ final class PathVC: BaseVC {
let inset: CGFloat = isIPhone5OrSmaller ? 64 : 80
let learnMoreButtonContainer = UIView(wrapping: learnMoreButton, withInsets: UIEdgeInsets(top: 0, leading: inset, bottom: 0, trailing: inset), shouldAdaptForIPadWithWidth: Values.iPadButtonWidth)
let attributionSpacer = UIView()
attributionSpacer.setContentHuggingPriority(.required, for: .vertical)
attributionSpacer.setContentCompressionResistancePriority(.required, for: .vertical)
attributionSpacer.set(.height, to: Values.smallSpacing)
let attributionLabel = UILabel()
attributionLabel.font = .systemFont(ofSize: Values.verySmallFontSize)
attributionLabel.text = "Path location data provided by MaxMind"
attributionLabel.themeTextColor = .textSecondary
attributionLabel.textAlignment = .center
attributionLabel.lineBreakMode = .byWordWrapping
attributionLabel.numberOfLines = 0
// Set up spacers
let topSpacer = UIView.vStretchingSpacer()
let bottomSpacer = UIView.vStretchingSpacer()
// Set up main stack view
let mainStackView = UIStackView(arrangedSubviews: [ explanationLabel, topSpacer, pathStackViewContainer, attributionSpacer, attributionLabel, bottomSpacer, learnMoreButtonContainer ])
let mainStackView = UIStackView(arrangedSubviews: [ explanationLabel, topSpacer, pathStackViewContainer, attributionLabel, bottomSpacer, learnMoreButtonContainer ])
mainStackView.axis = .vertical
mainStackView.alignment = .fill
mainStackView.layoutMargins = UIEdgeInsets(
@ -157,6 +156,7 @@ final class PathVC: BaseVC {
private func update(paths: [[LibSession.Snode]], force: Bool) {
guard let pathToDisplay: [LibSession.Snode] = paths.first else {
pathStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
attributionLabel.isHidden = true
spinner.startAnimating()
UIView.animate(withDuration: 0.25) {
@ -199,6 +199,7 @@ final class PathVC: BaseVC {
)
let rows = [ youRow ] + snodeRows + [ destinationRow ]
rows.forEach { pathStackView.addArrangedSubview($0) }
attributionLabel.isHidden = false
spinner.stopAnimating()
UIView.animate(withDuration: 0.25) {

@ -53,31 +53,47 @@ public final class ClosedGroupPoller: Poller {
}
override func nextPollDelay(for publicKey: String, using dependencies: Dependencies) -> TimeInterval {
// Get the received date of the last message in the thread. If we don't have
// any messages yet, pick some reasonable fake time interval to use instead
/// Get the received date of the last message in the thread. If we don't have any messages yet then use the group formation timestamp and,
/// if that is unable to be retrieved for some reason, fallback to an activity of 1 hour
let minActivityThreshold: TimeInterval = (5 * 60)
let maxActivityThreshold: TimeInterval = (12 * 60 * 60)
let fallbackActivityThreshold: TimeInterval = (1 * 60 * 60)
let lastMessageDate: Date = Storage.shared
.read { db in
try Interaction
let lastMessageTimestmapMs: Int64? = try Interaction
.filter(Interaction.Columns.threadId == publicKey)
.select(.receivedAtTimestampMs)
.order(Interaction.Columns.timestampMs.desc)
.asRequest(of: Int64.self)
.fetchOne(db)
switch lastMessageTimestmapMs {
case .some(let lastMessageTimestmapMs): return lastMessageTimestmapMs
case .none:
let formationTimestamp: TimeInterval? = try ClosedGroup
.filter(ClosedGroup.Columns.threadId == publicKey)
.select(.formationTimestamp)
.asRequest(of: TimeInterval.self)
.fetchOne(db)
return formationTimestamp.map { Int64(floor($0 * 1000)) }
}
}
.map { receivedAtTimestampMs -> Date? in
guard receivedAtTimestampMs > 0 else { return nil }
return Date(timeIntervalSince1970: (TimeInterval(receivedAtTimestampMs) / 1000))
}
.defaulting(to: Date().addingTimeInterval(-5 * 60))
.defaulting(to: dependencies.dateNow.addingTimeInterval(-fallbackActivityThreshold))
/// Convert the conversation activity frequency into
let timeSinceLastMessage: TimeInterval = dependencies.dateNow.timeIntervalSince(lastMessageDate)
let minPollInterval: Double = ClosedGroupPoller.minPollInterval
let limit: Double = (12 * 60 * 60)
let a: TimeInterval = ((ClosedGroupPoller.maxPollInterval - minPollInterval) / limit)
let nextPollInterval: TimeInterval = a * min(timeSinceLastMessage, limit) + minPollInterval
let conversationActivityInterval: TimeInterval = max(0, (timeSinceLastMessage - minActivityThreshold))
let activityIntervalDelta: Double = (maxActivityThreshold - minActivityThreshold)
let pollIntervalDelta: Double = (ClosedGroupPoller.maxPollInterval - ClosedGroupPoller.minPollInterval)
let activityIntervalPercentage: Double = min(1, (conversationActivityInterval / activityIntervalDelta))
return nextPollInterval
return (ClosedGroupPoller.minPollInterval + (pollIntervalDelta * activityIntervalPercentage))
}
override func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> PollerErrorResponse {

@ -54,13 +54,8 @@ public final class CurrentUserPoller: Poller {
override func nextPollDelay(for publicKey: String, using dependencies: Dependencies) -> TimeInterval {
let failureCount: TimeInterval = TimeInterval(failureCount.wrappedValue[publicKey] ?? 0)
// If there have been no failures then just use the 'minPollInterval'
guard failureCount > 0 else { return pollInterval }
// Otherwise use a simple back-off with the 'retryInterval'
let nextDelay: TimeInterval = (retryInterval * (failureCount * 1.2))
return min(maxRetryInterval, nextDelay)
// Scale the poll delay based on the number of failures
return min(maxRetryInterval, pollInterval + (retryInterval * (failureCount * 1.2)))
}
override func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> PollerErrorResponse {

@ -60,7 +60,6 @@ extension OpenGroupAPI {
let server: String = self.server
let originalRecursiveLoopId: UUID = self.recursiveLoopId
let lastPollStart: TimeInterval = dependencies.dateNow.timeIntervalSince1970
poll(using: dependencies)
.subscribe(on: Threading.communityPollerQueue, using: dependencies)
@ -77,27 +76,15 @@ extension OpenGroupAPI {
}
.defaulting(to: 0)
// Calculate the remaining poll delay
let currentTime: TimeInterval = dependencies.dateNow.timeIntervalSince1970
// Calculate the next poll delay
let nextPollInterval: TimeInterval = Poller.getInterval(
for: TimeInterval(minPollFailureCount),
minInterval: Poller.minPollInterval,
maxInterval: Poller.maxPollInterval
)
let remainingInterval: TimeInterval = max(0, nextPollInterval - (currentTime - lastPollStart))
// Schedule the next poll
guard remainingInterval > 0 else {
return Threading.communityPollerQueue.async(using: dependencies) {
// If we started a new recursive loop then we don't want to double up so just let this
// one stop looping
guard originalRecursiveLoopId == self?.recursiveLoopId else { return }
self?.pollRecursively(using: dependencies)
}
}
Threading.communityPollerQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default, using: dependencies) {
Threading.communityPollerQueue.asyncAfter(deadline: .now() + .milliseconds(Int(nextPollInterval * 1000)), qos: .default, using: dependencies) {
// If we started a new recursive loop then we don't want to double up so just let this
// one stop looping
guard originalRecursiveLoopId == self?.recursiveLoopId else { return }

@ -112,6 +112,7 @@ public class Poller {
let namespaces: [SnodeAPI.Namespace] = self.namespaces
let pollerQueue: DispatchQueue = self.pollerQueue
let lastPollStart: TimeInterval = dependencies.dateNow.timeIntervalSince1970
let fallbackPollDelay: TimeInterval = self.nextPollDelay(for: swarmPublicKey, using: dependencies)
// Store the publisher intp the cancellables dictionary
cancellables.mutate { [weak self] cancellables in
@ -134,49 +135,57 @@ public class Poller {
// If the polling has been cancelled then don't continue
guard self?.isPolling.wrappedValue[swarmPublicKey] == true else { return }
let endTime: TimeInterval = dependencies.dateNow.timeIntervalSince1970
// Increment or reset the failureCount
let failureCount: Int
switch result {
case .failure:
failureCount = (self?.failureCount
.mutate {
let updatedFailureCount: Int = (($0[swarmPublicKey] ?? 0) + 1)
$0[swarmPublicKey] = updatedFailureCount
return updatedFailureCount
})
.defaulting(to: -1)
case .success:
failureCount = 0
self?.failureCount.mutate { $0.removeValue(forKey: swarmPublicKey) }
}
// Log information about the poll
let endTime: TimeInterval = dependencies.dateNow.timeIntervalSince1970
let duration: TimeUnit = .seconds(endTime - lastPollStart)
let nextPollInterval: TimeUnit = .seconds((self?.nextPollDelay(for: swarmPublicKey, using: dependencies))
.defaulting(to: fallbackPollDelay))
switch result {
case .failure(let error):
// Determine if the error should stop us from polling anymore
switch self?.handlePollError(error, for: swarmPublicKey, using: dependencies) {
case .stopPolling: return
case .continuePollingInfo(let info):
Log.error("\(pollerName) failed to process any messages due to error: \(error). \(info)")
Log.error("\(pollerName) failed to process any messages after \(duration, unit: .s) due to error: \(error). \(info). Setting failure count to \(failureCount). Next poll in \(nextPollInterval, unit: .s).")
case .continuePolling, .none:
Log.error("\(pollerName) failed to process any messages due to error: \(error).")
Log.error("\(pollerName) failed to process any messages after \(duration, unit: .s) due to error: \(error). Setting failure count to \(failureCount). Next poll in \(nextPollInterval, unit: .s).")
}
case .success(let response):
let duration: TimeUnit = .seconds(endTime - lastPollStart)
switch (response.rawMessageCount, response.validMessageCount, response.hadValidHashUpdate) {
case (0, _, _):
Log.info("Received no new messages in \(pollerName) after \(duration, unit: .s).")
Log.info("Received no new messages in \(pollerName) after \(duration, unit: .s). Next poll in \(nextPollInterval, unit: .s).")
case (_, 0, false):
Log.info("Received \(response.rawMessageCount) new message(s) in \(pollerName) after \(duration, unit: .s), all duplicates - marked the hash we polled with as invalid")
Log.info("Received \(response.rawMessageCount) new message(s) in \(pollerName) after \(duration, unit: .s), all duplicates - marked the hash we polled with as invalid. Next poll in \(nextPollInterval, unit: .s).")
default:
Log.info("Received \(response.validMessageCount) new message(s) in \(pollerName) after \(duration, unit: .s) (duplicates: \(response.rawMessageCount - response.validMessageCount))")
Log.info("Received \(response.validMessageCount) new message(s) in \(pollerName) after \(duration, unit: .s) (duplicates: \(response.rawMessageCount - response.validMessageCount)). Next poll in \(nextPollInterval, unit: .s).")
}
}
// Calculate the remaining poll delay and schedule the next poll
guard
self != nil,
let remainingInterval: TimeInterval = (self?.nextPollDelay(for: swarmPublicKey, using: dependencies))
.map({ nextPollInterval in max(0, nextPollInterval - (endTime - lastPollStart)) }),
remainingInterval > 0
else {
return pollerQueue.async(using: dependencies) {
self?.pollRecursively(for: swarmPublicKey, drainBehaviour: drainBehaviour, using: dependencies)
}
}
pollerQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default, using: dependencies) {
// Schedule the next poll
pollerQueue.asyncAfter(deadline: .now() + .milliseconds(Int(nextPollInterval.timeInterval * 1000)), qos: .default, using: dependencies) {
self?.pollRecursively(for: swarmPublicKey, drainBehaviour: drainBehaviour, using: dependencies)
}
}

@ -14,7 +14,7 @@ public enum TimeUnit: Equatable, CustomStringConvertible {
case days(Double)
case weeks(Double)
var timeInterval: TimeInterval {
public var timeInterval: TimeInterval {
switch self {
case .nanoseconds(let value): return (value * 1e-9)
case .microseconds(let value): return (value * 1e-6)

Loading…
Cancel
Save