Skip to content

Commit b9c6f9e

Browse files
committed
Updated tracking / transitions of the task's version state.
1 parent 3ac305e commit b9c6f9e

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
@@ -689,17 +689,9 @@ class TaskBackend {
689689

690690
zone = versionState.zone!;
691691
instance = versionState.instance!;
692-
693-
// Remove instanceName, zone, secretToken, and set attempts = 0
694-
state.versions![version] = PackageVersionStateInfo(
695-
scheduled: versionState.scheduled,
692+
state.versions![version] = versionState.complete(
696693
docs: hasDocIndexHtml,
697694
pana: summary != null,
698-
finished: true,
699-
attempts: 0,
700-
instance: null, // version is no-longer running on this instance
701-
secretToken: null, // TODO: Consider retaining this for idempotency
702-
zone: null,
703695
);
704696

705697
// Determine if something else was running on the instance
@@ -1002,13 +994,12 @@ class TaskBackend {
1002994
await for (final state in _db.tasks.listAllForCurrentRuntime()) {
1003995
final zone = taskWorkerCloudCompute.zones.first;
1004996
// ignore: invalid_use_of_visible_for_testing_member
1005-
final updated = await updatePackageStateWithPendingVersions(
997+
final payload = await updatePackageStateWithPendingVersions(
1006998
_db,
1007999
state.package,
10081000
zone,
10091001
taskWorkerCloudCompute.generateInstanceName(),
10101002
);
1011-
final payload = updated?.$1;
10121003
if (payload == null) continue;
10131004
await processPayload(payload);
10141005
}
@@ -1418,7 +1409,6 @@ final class _TaskDataAccess {
14181409
Future<void> restorePreviousVersionsState(
14191410
String packageName,
14201411
String instanceName,
1421-
Map<String, PackageVersionStateInfo> previousVersionsMap,
14221412
) async {
14231413
await withRetryTransaction(_db, (tx) async {
14241414
final s = await tx.tasks.lookupOrNull(packageName);
@@ -1429,7 +1419,7 @@ final class _TaskDataAccess {
14291419
s.versions!.addEntries(
14301420
s.versions!.entries
14311421
.where((e) => e.value.instance == instanceName)
1432-
.map((e) => MapEntry(e.key, previousVersionsMap[e.key]!)),
1422+
.map((e) => MapEntry(e.key, e.value.resetAfterFailedAttempt())),
14331423
);
14341424
s.pendingAt = derivePendingAt(
14351425
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)