33
44using System ;
55using System . Globalization ;
6+ using System . Linq ;
67using System . Net ;
78using System . Threading ;
89using System . Threading . Tasks ;
1516
1617namespace osu . Server . Queues . ScoreStatisticsProcessor . Commands . Maintenance
1718{
18- [ Command ( "cleanup " , Description = "Delete non-preserved scores which are stale enough." ) ]
19+ [ Command ( "delete-non-preserved " , Description = "Delete non-preserved scores which are stale enough." ) ]
1920 public class DeleteNonPreservedScoresCommand
2021 {
2122 /// <summary>
@@ -30,44 +31,65 @@ public async Task<int> OnExecuteAsync(CancellationToken cancellationToken)
3031 using ( var deleteCommand = deleteConnection . CreateCommand ( ) )
3132 using ( var s3 = S3 . GetClient ( ) )
3233 {
34+ // TODO: for safety do we want to delete pins here? might be a race condition where user pins right as this process is running.
3335 deleteCommand . CommandText = "DELETE FROM scores WHERE id = @id;" ;
3436
3537 MySqlParameter scoreId = deleteCommand . Parameters . Add ( "id" , MySqlDbType . UInt64 ) ;
3638
3739 await deleteCommand . PrepareAsync ( cancellationToken ) ;
3840
39- var scores = await readConnection . QueryAsync < SoloScore > ( new CommandDefinition (
40- $ "SELECT * FROM `scores` WHERE `preserve` = 0 AND `unix_updated_at` < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL { preserve_hours } HOUR))", flags : CommandFlags . None , cancellationToken : cancellationToken ) ) ;
41-
42- foreach ( var score in scores )
41+ while ( ! cancellationToken . IsCancellationRequested )
4342 {
44- if ( cancellationToken . IsCancellationRequested )
45- break ;
43+ var scores = await readConnection . QueryAsync < SoloScore > ( new CommandDefinition (
44+ $ "SELECT * FROM `scores` WHERE `preserve` = 0 AND `unix_updated_at` < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL { preserve_hours } HOUR)) LIMIT 1000",
45+ cancellationToken : cancellationToken ) ) ;
4646
47- Console . WriteLine ( $ "Deleting score { score . id } ...") ;
48- scoreId . Value = score . id ;
49- await deleteCommand . ExecuteNonQueryAsync ( cancellationToken ) ;
47+ if ( ! scores . Any ( ) )
48+ break ;
5049
51- // TODO: check pins
50+ Console . WriteLine ( $ "Processing next batch of { scores . Count ( ) } scores" ) ;
5251
53- if ( score . has_replay )
52+ foreach ( var score in scores )
5453 {
55- Console . WriteLine ( "* Removing replay from S3..." ) ;
56- var deleteResult = await s3 . DeleteObjectAsync ( S3 . REPLAYS_BUCKET , score . id . ToString ( CultureInfo . InvariantCulture ) , cancellationToken ) ;
54+ if ( cancellationToken . IsCancellationRequested )
55+ break ;
56+
57+ Console . WriteLine ( $ "Deleting score { score . id } ...") ;
5758
58- switch ( deleteResult . HttpStatusCode )
59+ if ( score . has_replay )
5960 {
60- case HttpStatusCode . NoContent :
61- // below wording is intentionally very roundabout, because s3 does not actually really seem to produce the types of error you'd expect.
62- // for instance, even if you request removal of a nonexistent object, it'll just throw a 204 No Content back
63- // with no real way to determine whether it actually even did anything.
64- Console . WriteLine ( "* Deletion request completed without error." ) ;
65- break ;
66-
67- default :
68- await Console . Error . WriteLineAsync ( $ "* Received unexpected status code when attempting to delete replay: { deleteResult . HttpStatusCode } .") ;
69- break ;
61+ if ( score . is_legacy_score )
62+ {
63+ // TODO: we likely do want logic here to handle the cleanup of web-10 (at least the replay part?).
64+ // for now, make sure we don't attempt to clean up stable scores with replays here.
65+ throw new InvalidOperationException ( $ "Legacy score id:{ score . id } legacy_id:{ score . legacy_score_id } has replay flag set") ;
66+ }
67+
68+ Console . WriteLine ( "* Removing replay from S3..." ) ;
69+ var deleteResult = await s3 . DeleteObjectAsync ( S3 . REPLAYS_BUCKET , score . id . ToString ( CultureInfo . InvariantCulture ) , cancellationToken ) ;
70+
71+ switch ( deleteResult . HttpStatusCode )
72+ {
73+ case HttpStatusCode . NoContent :
74+ // below wording is intentionally very roundabout, because s3 does not actually really seem to produce the types of error you'd expect.
75+ // for instance, even if you request removal of a nonexistent object, it'll just throw a 204 No Content back
76+ // with no real way to determine whether it actually even did anything.
77+ Console . WriteLine ( "* Deletion request completed without error." ) ;
78+ break ;
79+
80+ default :
81+ await Console . Error . WriteLineAsync ( $ "* Received unexpected status code when attempting to delete replay: { deleteResult . HttpStatusCode } .") ;
82+ break ;
83+ }
7084 }
85+
86+ // TODO: as long as we're doing partition cycling, this is redundant.
87+ // in fact, we could move this inside the replay check. or we could update the initial query to only return `has_replay = 1` for cleanup.
88+ //
89+ // said another way, this whole method exists for the sole purpose of deleting attached replay data.
90+ // deleting the score is only really useful here as a marker that we've cleaned up the replay.
91+ scoreId . Value = score . id ;
92+ await deleteCommand . ExecuteNonQueryAsync ( cancellationToken ) ;
7193 }
7294 }
7395 }
0 commit comments