Skip to content

Commit 01d362e

Browse files
committed
Updated tracking / transitions of the task's version state.
1 parent 3392d5b commit 01d362e

File tree

4 files changed

+68
-37
lines changed

4 files changed

+68
-37
lines changed

app/lib/task/backend.dart

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -699,17 +699,9 @@ class TaskBackend {
699699

700700
zone = versionState.zone!;
701701
instance = versionState.instance!;
702-
703-
// Remove instanceName, zone, secretToken, and set attempts = 0
704-
state.versions![version] = PackageVersionStateInfo(
705-
scheduled: versionState.scheduled,
702+
state.versions![version] = versionState.complete(
706703
docs: hasDocIndexHtml,
707704
pana: summary != null,
708-
finished: true,
709-
attempts: 0,
710-
instance: null, // version is no-longer running on this instance
711-
secretToken: null, // TODO: Consider retaining this for idempotency
712-
zone: null,
713705
);
714706

715707
// Determine if something else was running on the instance
@@ -1012,13 +1004,12 @@ class TaskBackend {
10121004
await for (final state in _db.tasks.listAllForCurrentRuntime()) {
10131005
final zone = taskWorkerCloudCompute.zones.first;
10141006
// ignore: invalid_use_of_visible_for_testing_member
1015-
final updated = await updatePackageStateWithPendingVersions(
1007+
final payload = await updatePackageStateWithPendingVersions(
10161008
_db,
10171009
state.package,
10181010
zone,
10191011
taskWorkerCloudCompute.generateInstanceName(),
10201012
);
1021-
final payload = updated?.$1;
10221013
if (payload == null) continue;
10231014
await processPayload(payload);
10241015
}
@@ -1428,7 +1419,6 @@ final class _TaskDataAccess {
14281419
Future<void> restorePreviousVersionsState(
14291420
String packageName,
14301421
String instanceName,
1431-
Map<String, PackageVersionStateInfo> previousVersionsMap,
14321422
) async {
14331423
await withRetryTransaction(_db, (tx) async {
14341424
final s = await tx.tasks.lookupOrNull(packageName);
@@ -1439,7 +1429,7 @@ final class _TaskDataAccess {
14391429
s.versions!.addEntries(
14401430
s.versions!.entries
14411431
.where((e) => e.value.instance == instanceName)
1442-
.map((e) => MapEntry(e.key, previousVersionsMap[e.key]!)),
1432+
.map((e) => MapEntry(e.key, e.value.resetAfterFailedAttempt())),
14431433
);
14441434
s.pendingAt = derivePendingAt(
14451435
versions: s.versions!,

app/lib/task/models.dart

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:convert' show json;
6+
import 'dart:math';
67

78
import 'package:clock/clock.dart';
89
import 'package:json_annotation/json_annotation.dart';
@@ -249,7 +250,7 @@ List<String> derivePendingVersions({
249250
}
250251

251252
/// State of a given `version` within a [PackageState].
252-
@JsonSerializable()
253+
@JsonSerializable(includeIfNull: false)
253254
class PackageVersionStateInfo {
254255
PackageVersionStatus get status {
255256
if (attempts == 0 && scheduled == initialTimestamp) {
@@ -319,6 +320,9 @@ class PackageVersionStateInfo {
319320
/// comparison. Please use [isAuthorized] for validating a request.
320321
final String? secretToken;
321322

323+
/// The previous scheduled timestamp (if we are currently in an active schedule).
324+
final DateTime? previousScheduled;
325+
322326
/// Return true, if [token] matches [secretToken] and it has not expired.
323327
///
324328
/// This does a fixed-time comparison to mitigate timing attacks.
@@ -347,6 +351,7 @@ class PackageVersionStateInfo {
347351
this.docs = false,
348352
this.pana = false,
349353
this.finished = false,
354+
this.previousScheduled,
350355
});
351356

352357
factory PackageVersionStateInfo.fromJson(Map<String, dynamic> m) =>
@@ -364,6 +369,53 @@ class PackageVersionStateInfo {
364369
'secretToken: $secretToken',
365370
].join(', ') +
366371
')';
372+
373+
// Remove instanceName, zone, secretToken, and set attempts = 0
374+
PackageVersionStateInfo complete({required bool pana, required bool docs}) {
375+
return PackageVersionStateInfo(
376+
scheduled: scheduled,
377+
attempts: 0,
378+
docs: docs,
379+
pana: pana,
380+
finished: true,
381+
zone: null,
382+
instance: null, // version is no-longer running on this instance
383+
secretToken: null, // TODO: Consider retaining this for idempotency
384+
previousScheduled: null,
385+
);
386+
}
387+
388+
/// Derives a new version state with scheduling information.
389+
PackageVersionStateInfo scheduleNew({
390+
required String zone,
391+
required String instanceName,
392+
}) {
393+
return PackageVersionStateInfo(
394+
scheduled: clock.now(),
395+
attempts: attempts + 1,
396+
zone: zone,
397+
instance: instanceName,
398+
secretToken: createUuid(),
399+
finished: finished,
400+
docs: docs,
401+
pana: pana,
402+
previousScheduled: scheduled,
403+
);
404+
}
405+
406+
/// Reverts the status of the last scheduling attempt, which has presumably failed.
407+
PackageVersionStateInfo resetAfterFailedAttempt() {
408+
return PackageVersionStateInfo(
409+
scheduled: previousScheduled ?? initialTimestamp,
410+
attempts: max(0, attempts - 1),
411+
zone: null,
412+
instance: null,
413+
secretToken: null,
414+
finished: finished,
415+
docs: docs,
416+
pana: pana,
417+
);
418+
}
367419
}
368420

369421
/// A [db.Property] encoding a Map from version to [PackageVersionStateInfo] as JSON.

app/lib/task/models.g.dart

Lines changed: 7 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/lib/task/scheduler.dart

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import 'package:meta/meta.dart';
1010
import 'package:pub_dev/package/backend.dart';
1111
import 'package:pub_dev/shared/configuration.dart';
1212
import 'package:pub_dev/shared/datastore.dart';
13-
import 'package:pub_dev/shared/utils.dart';
1413
import 'package:pub_dev/task/backend.dart';
1514
import 'package:pub_dev/task/cloudcompute/cloudcompute.dart';
1615
import 'package:pub_dev/task/models.dart';
@@ -101,13 +100,12 @@ Future<(CreateInstancesState, Duration)> runOneCreateInstancesCycle(
101100
final instanceName = compute.generateInstanceName();
102101
final zone = pickZone();
103102

104-
final updated = await updatePackageStateWithPendingVersions(
103+
final payload = await updatePackageStateWithPendingVersions(
105104
db,
106105
selected.package,
107106
zone,
108107
instanceName,
109108
);
110-
final payload = updated?.$1;
111109
if (payload == null) {
112110
return;
113111
}
@@ -174,15 +172,13 @@ Future<(CreateInstancesState, Duration)> runOneCreateInstancesCycle(
174172
banZone(zone, minutes: 15);
175173
}
176174
if (rollbackPackageState) {
177-
final oldVersionsMap = updated?.$2 ?? const {};
178-
// Restore the state of the PackageState for versions that were
175+
// Restire the state of the PackageState for versions that were
179176
// suppose to run on the instance we just failed to create.
180177
// If this doesn't work, we'll eventually retry. Hence, correctness
181178
// does not hinge on this transaction being successful.
182179
await db.tasks.restorePreviousVersionsState(
183180
selected.package,
184181
instanceName,
185-
oldVersionsMap,
186182
);
187183
}
188184
}
@@ -221,11 +217,8 @@ Future<(CreateInstancesState, Duration)> runOneCreateInstancesCycle(
221217

222218
/// Updates the package state with versions that are already pending or
223219
/// will be pending soon.
224-
///
225-
/// Returns the payload and the old status of the state info version map
226220
@visibleForTesting
227-
Future<(Payload, Map<String, PackageVersionStateInfo>)?>
228-
updatePackageStateWithPendingVersions(
221+
Future<Payload?> updatePackageStateWithPendingVersions(
229222
DatastoreDB db,
230223
String package,
231224
String zone,
@@ -237,7 +230,6 @@ updatePackageStateWithPendingVersions(
237230
// presumably the package was deleted.
238231
return null;
239232
}
240-
final oldVersionsMap = {...?s.versions};
241233

242234
final now = clock.now();
243235
final pendingVersions = derivePendingVersions(
@@ -253,14 +245,7 @@ updatePackageStateWithPendingVersions(
253245
// Update PackageState
254246
s.versions!.addAll({
255247
for (final v in pendingVersions.map((v) => v.toString()))
256-
v: PackageVersionStateInfo(
257-
scheduled: now,
258-
attempts: s.versions![v]!.attempts + 1,
259-
zone: zone,
260-
instance: instanceName,
261-
secretToken: createUuid(),
262-
finished: s.versions![v]!.finished,
263-
),
248+
v: s.versions![v]!.scheduleNew(zone: zone, instanceName: instanceName),
264249
});
265250
s.pendingAt = derivePendingAt(
266251
versions: s.versions!,
@@ -279,6 +264,6 @@ updatePackageStateWithPendingVersions(
279264
),
280265
),
281266
);
282-
return (payload, oldVersionsMap);
267+
return payload;
283268
});
284269
}

0 commit comments

Comments
 (0)