Skip to content

Commit 4848e34

Browse files
authored
Move PackageState derive* methods out of the model class. (#9066)
1 parent 4548c55 commit 4848e34

File tree

3 files changed

+133
-103
lines changed

3 files changed

+133
-103
lines changed

app/lib/task/backend.dart

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import 'package:pub_dev/task/models.dart'
5050
PackageStateInfo,
5151
PackageVersionStateInfo,
5252
PackageVersionStatus,
53+
derivePendingAt,
5354
initialTimestamp,
5455
maxTaskExecutionTime;
5556
import 'package:pub_dev/task/scheduler.dart';
@@ -394,21 +395,25 @@ class TaskBackend {
394395
if (state == null) {
395396
// Create [PackageState] entity to track the package
396397
_log.info('Started state tracking for $packageName');
398+
final versionsMap = {
399+
for (final version in versions)
400+
version: PackageVersionStateInfo(
401+
scheduled: initialTimestamp,
402+
attempts: 0,
403+
),
404+
};
397405
await tx.tasks.insert(
398406
PackageState()
399407
..setId(runtimeVersion, packageName)
400408
..runtimeVersion = runtimeVersion
401-
..versions = {
402-
for (final version in versions)
403-
version: PackageVersionStateInfo(
404-
scheduled: initialTimestamp,
405-
attempts: 0,
406-
),
407-
}
409+
..versions = versionsMap
408410
..dependencies = <String>[]
409411
..lastDependencyChanged = initialTimestamp
410412
..finished = initialTimestamp
411-
..derivePendingAt(),
413+
..pendingAt = derivePendingAt(
414+
versions: versionsMap,
415+
lastDependencyChanged: initialTimestamp,
416+
),
412417
);
413418
return true; // no more work for this package, state is synced
414419
}
@@ -464,7 +469,10 @@ class TaskBackend {
464469
attempts: 0,
465470
),
466471
});
467-
state.derivePendingAt();
472+
state.pendingAt = derivePendingAt(
473+
versions: state.versions!,
474+
lastDependencyChanged: state.lastDependencyChanged!,
475+
);
468476

469477
_log.info('Update state tracking for $packageName');
470478
await tx.tasks.update(state);
@@ -726,7 +734,10 @@ class TaskBackend {
726734

727735
// Ensure that we update [state.pendingAt], otherwise it might be
728736
// re-scheduled way too soon.
729-
state.derivePendingAt();
737+
state.pendingAt = derivePendingAt(
738+
versions: state.versions!,
739+
lastDependencyChanged: state.lastDependencyChanged!,
740+
);
730741
state.finished = clock.now().toUtc();
731742

732743
await tx.tasks.update(state);
@@ -1407,7 +1418,10 @@ final class _TaskDataAccess {
14071418
tx.insert(
14081419
s
14091420
..lastDependencyChanged = publishedAt
1410-
..derivePendingAt(),
1421+
..pendingAt = derivePendingAt(
1422+
versions: s.versions!,
1423+
lastDependencyChanged: publishedAt,
1424+
),
14111425
);
14121426
return true;
14131427
}
@@ -1442,7 +1456,10 @@ final class _TaskDataAccess {
14421456
.where((e) => e.value.instance == instanceName)
14431457
.map((e) => MapEntry(e.key, previousVersionsMap[e.key]!)),
14441458
);
1445-
s.derivePendingAt();
1459+
s.pendingAt = derivePendingAt(
1460+
versions: s.versions!,
1461+
lastDependencyChanged: s.lastDependencyChanged!,
1462+
);
14461463
await tx.tasks.update(s);
14471464
});
14481465
}

app/lib/task/models.dart

Lines changed: 95 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -133,95 +133,6 @@ class PackageState extends db.ExpandoModel<String> {
133133
@db.DateTimeProperty(required: true, indexed: true)
134134
DateTime finished = initialTimestamp;
135135

136-
/// Derive [pendingAt] using [versions] and [lastDependencyChanged].
137-
///
138-
/// When updating PackageState the pendingAt property is set to the minimum of:
139-
/// * `scheduled + 31 days` for any version,
140-
/// * `scheduled + 24 hours` for any version where `dependencyChanged > scheduled`
141-
/// * `scheduled + 3 hours * attempts^2` for any version where `attempts > 0 && attempts < 3`.
142-
void derivePendingAt() {
143-
final versionStates = versions!.values;
144-
pendingAt = [
145-
// scheduled + 31 days
146-
...versionStates.map((v) => v.scheduled.add(taskRetriggerInterval)),
147-
// scheduled + 24 hours, where scheduled < lastDependencyChanged
148-
...versionStates
149-
.where((v) => v.scheduled.isBefore(lastDependencyChanged!))
150-
.map((v) => v.scheduled.add(taskDependencyRetriggerCoolOff)),
151-
// scheduled + 3 hours * attempts^2, where attempts > 0 && attempts < 3
152-
...versionStates
153-
.where((v) => v.attempts > 0 && v.attempts < taskRetryLimit)
154-
.map((v) => v.scheduled.add(taskRetryDelay(v.attempts))),
155-
// Pick the minimum of the candidates, default scheduling in year 3k
156-
// if there is no date before that.
157-
].fold(DateTime(3000), (a, b) => a!.isBefore(b) ? a : b);
158-
}
159-
160-
/// Return a list of pending versions for this package.
161-
///
162-
/// When scheduling analysis of a package we piggyback along versions that
163-
/// are going to be pending soon too. Hence, we return a version if:
164-
/// * `now - scheduled > 21 days`,
165-
/// * `lastDependencyChanged > scheduled`, or,
166-
/// * `attempts > 0 && attempts < 3 && now - scheduled > 3 hours * attempts^2`
167-
List<String> pendingVersions({DateTime? at}) {
168-
final at_ = at ?? clock.now();
169-
Duration timeSince(DateTime past) => at_.difference(past);
170-
171-
final list = versions!.entries
172-
.where(
173-
// NOTE: Any changes here must be reflected in [derivePendingAt]
174-
(e) =>
175-
// If scheduled more than 21 days ago
176-
timeSince(e.value.scheduled) > minTaskRetriggerInterval ||
177-
// If a dependency has changed since it was last scheduled
178-
lastDependencyChanged!.isAfter(e.value.scheduled) ||
179-
// If:
180-
// - attempts > 0 (analysis is not done, and has been started)
181-
// - no more than 3 attempts have been done,
182-
// - now - scheduled > 3 hours * attempts^2
183-
(e.value.attempts > 0 &&
184-
e.value.attempts < taskRetryLimit &&
185-
timeSince(e.value.scheduled) >
186-
taskRetryDelay(e.value.attempts)),
187-
)
188-
.map((e) => e.key)
189-
.map(Version.parse)
190-
.toList();
191-
192-
// Prioritize stable versions first, prereleases after them (in decreasing order), e.g.
193-
// - 2.5.0
194-
// - 2.4.0
195-
// - 2.0.0
196-
// - 1.2.0
197-
// - 3.0.0-dev2
198-
// - 3.0.0-dev1
199-
// - 2.7.0-beta
200-
// - 1.0.0-dev
201-
list.sort((a, b) => compareSemanticVersionsDesc(a, b, true, true));
202-
203-
// Promote the first prerelease version to the second position, e.g.
204-
// - 2.5.0
205-
// - 3.0.0-dev2
206-
// - 2.4.0
207-
// - 2.0.0
208-
// - 1.2.0
209-
// - 3.0.0-dev1
210-
// - 2.7.0-beta
211-
// - 1.0.0-dev
212-
//
213-
// (applicable only when the second position is a stable version)
214-
if (list.length > 2 && !list[1].isPreRelease) {
215-
final firstPrereleaseIndex = list.indexWhere((v) => v.isPreRelease);
216-
if (firstPrereleaseIndex > 1) {
217-
final v = list.removeAt(firstPrereleaseIndex);
218-
list.insert(1, v);
219-
}
220-
}
221-
222-
return list.map((s) => s.toString()).toList();
223-
}
224-
225136
/// Returns true if the current [PackageState] instance is new, no version analysis
226137
/// has not completed yet (with neither success nor failure).
227138
bool get hasNeverFinished => finished == initialTimestamp;
@@ -242,6 +153,101 @@ class PackageState extends db.ExpandoModel<String> {
242153
'\n)';
243154
}
244155

156+
/// Derive the `pendingAt` field using [versions] and [lastDependencyChanged].
157+
///
158+
/// When updating PackageState the pendingAt property is set to the minimum of:
159+
/// * `scheduled + 31 days` for any version,
160+
/// * `scheduled + 24 hours` for any version where `dependencyChanged > scheduled`
161+
/// * `scheduled + 3 hours * attempts^2` for any version where `attempts > 0 && attempts < 3`.
162+
DateTime derivePendingAt({
163+
required Map<String, PackageVersionStateInfo> versions,
164+
required DateTime lastDependencyChanged,
165+
}) {
166+
return [
167+
// scheduled + 31 days
168+
...versions.values.map((v) => v.scheduled.add(taskRetriggerInterval)),
169+
// scheduled + 24 hours, where scheduled < lastDependencyChanged
170+
...versions.values
171+
.where((v) => v.scheduled.isBefore(lastDependencyChanged))
172+
.map((v) => v.scheduled.add(taskDependencyRetriggerCoolOff)),
173+
// scheduled + 3 hours * attempts^2, where attempts > 0 && attempts < 3
174+
...versions.values
175+
.where((v) => v.attempts > 0 && v.attempts < taskRetryLimit)
176+
.map((v) => v.scheduled.add(taskRetryDelay(v.attempts))),
177+
// Pick the minimum of the candidates, default scheduling in year 3k
178+
// if there is no date before that.
179+
].fold(DateTime(3000), (a, b) => a.isBefore(b) ? a : b);
180+
}
181+
182+
/// Return a list of pending versions for this package.
183+
///
184+
/// When scheduling analysis of a package we piggyback along versions that
185+
/// are going to be pending soon too. Hence, we return a version if:
186+
/// * `now - scheduled > 21 days`,
187+
/// * `lastDependencyChanged > scheduled`, or,
188+
/// * `attempts > 0 && attempts < 3 && now - scheduled > 3 hours * attempts^2`
189+
List<String> derivePendingVersions({
190+
required Map<String, PackageVersionStateInfo> versions,
191+
required DateTime lastDependencyChanged,
192+
required DateTime? at,
193+
}) {
194+
final at_ = at ?? clock.now();
195+
Duration timeSince(DateTime past) => at_.difference(past);
196+
197+
final list = versions.entries
198+
.where(
199+
// NOTE: Any changes here must be reflected in [derivePendingAt]
200+
(e) =>
201+
// If scheduled more than 21 days ago
202+
timeSince(e.value.scheduled) > minTaskRetriggerInterval ||
203+
// If a dependency has changed since it was last scheduled
204+
lastDependencyChanged.isAfter(e.value.scheduled) ||
205+
// If:
206+
// - attempts > 0 (analysis is not done, and has been started)
207+
// - no more than 3 attempts have been done,
208+
// - now - scheduled > 3 hours * attempts^2
209+
(e.value.attempts > 0 &&
210+
e.value.attempts < taskRetryLimit &&
211+
timeSince(e.value.scheduled) >
212+
taskRetryDelay(e.value.attempts)),
213+
)
214+
.map((e) => e.key)
215+
.map(Version.parse)
216+
.toList();
217+
218+
// Prioritize stable versions first, prereleases after them (in decreasing order), e.g.
219+
// - 2.5.0
220+
// - 2.4.0
221+
// - 2.0.0
222+
// - 1.2.0
223+
// - 3.0.0-dev2
224+
// - 3.0.0-dev1
225+
// - 2.7.0-beta
226+
// - 1.0.0-dev
227+
list.sort((a, b) => compareSemanticVersionsDesc(a, b, true, true));
228+
229+
// Promote the first prerelease version to the second position, e.g.
230+
// - 2.5.0
231+
// - 3.0.0-dev2
232+
// - 2.4.0
233+
// - 2.0.0
234+
// - 1.2.0
235+
// - 3.0.0-dev1
236+
// - 2.7.0-beta
237+
// - 1.0.0-dev
238+
//
239+
// (applicable only when the second position is a stable version)
240+
if (list.length > 2 && !list[1].isPreRelease) {
241+
final firstPrereleaseIndex = list.indexWhere((v) => v.isPreRelease);
242+
if (firstPrereleaseIndex > 1) {
243+
final v = list.removeAt(firstPrereleaseIndex);
244+
list.insert(1, v);
245+
}
246+
}
247+
248+
return list.map((s) => s.toString()).toList();
249+
}
250+
245251
/// State of a given `version` within a [PackageState].
246252
@JsonSerializable()
247253
class PackageVersionStateInfo {

app/lib/task/scheduler.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,11 @@ updatePackageStateWithPendingVersions(
286286
final oldVersionsMap = {...?s.versions};
287287

288288
final now = clock.now();
289-
final pendingVersions = s.pendingVersions(at: now).toList();
289+
final pendingVersions = derivePendingVersions(
290+
versions: s.versions!,
291+
lastDependencyChanged: s.lastDependencyChanged!,
292+
at: now,
293+
).toList();
290294
if (pendingVersions.isEmpty) {
291295
// do not schedule anything
292296
return null;
@@ -304,7 +308,10 @@ updatePackageStateWithPendingVersions(
304308
finished: s.versions![v]!.finished,
305309
),
306310
});
307-
s.derivePendingAt();
311+
s.pendingAt = derivePendingAt(
312+
versions: s.versions!,
313+
lastDependencyChanged: s.lastDependencyChanged!,
314+
);
308315
await tx.tasks.update(s);
309316

310317
// Create payload

0 commit comments

Comments
 (0)