@@ -35,10 +35,12 @@ pub enum StateFileStatus {
35
35
NotRead ,
36
36
}
37
37
38
- enum AllExercisesCheck {
39
- Pending ( usize ) ,
40
- AllDone ,
41
- CheckedUntil ( usize ) ,
38
+ #[ derive( Clone , Copy , PartialEq ) ]
39
+ enum AllExercisesResult {
40
+ Pending ,
41
+ Success ,
42
+ Failed ,
43
+ Error ,
42
44
}
43
45
44
46
pub struct AppState {
@@ -270,18 +272,32 @@ impl AppState {
270
272
self . write ( )
271
273
}
272
274
273
- pub fn set_pending ( & mut self , exercise_ind : usize ) -> Result < ( ) > {
275
+ // Set the status of an exercise without saving. Returns `true` if the
276
+ // status actually changed (and thus needs saving later)
277
+ pub fn set_status ( & mut self , exercise_ind : usize , done : bool ) -> Result < bool > {
274
278
let exercise = self
275
279
. exercises
276
280
. get_mut ( exercise_ind)
277
281
. context ( BAD_INDEX_ERR ) ?;
278
282
279
- if exercise. done {
280
- exercise. done = false ;
281
- self . n_done -= 1 ;
282
- self . write ( ) ?;
283
+ if exercise. done == done {
284
+ Ok ( false )
285
+ } else {
286
+ exercise. done = done;
287
+ if done {
288
+ self . n_done += 1 ;
289
+ } else {
290
+ self . n_done -= 1 ;
291
+ }
292
+ Ok ( true )
283
293
}
294
+ }
284
295
296
+ // Set the status of an exercise to "pending" and save
297
+ pub fn set_pending ( & mut self , exercise_ind : usize ) -> Result < ( ) > {
298
+ if self . set_status ( exercise_ind, false ) ? {
299
+ self . write ( ) ?;
300
+ }
285
301
Ok ( ( ) )
286
302
}
287
303
@@ -380,11 +396,11 @@ impl AppState {
380
396
}
381
397
382
398
// Return the exercise index of the first pending exercise found.
383
- fn check_all_exercises ( & self , stdout : & mut StdoutLock ) -> Result < Option < usize > > {
399
+ fn check_all_exercises ( & mut self , stdout : & mut StdoutLock ) -> Result < Option < usize > > {
384
400
stdout. write_all ( FINAL_CHECK_MSG ) ?;
385
401
let n_exercises = self . exercises . len ( ) ;
386
402
387
- let status = thread:: scope ( |s| {
403
+ let ( mut checked_count , mut results ) = thread:: scope ( |s| {
388
404
let handles = self
389
405
. exercises
390
406
. iter ( )
@@ -394,48 +410,83 @@ impl AppState {
394
410
} )
395
411
. collect :: < Vec < _ > > ( ) ;
396
412
413
+ let mut results = vec ! [ AllExercisesResult :: Pending ; n_exercises] ;
414
+ let mut checked_count = 0 ;
397
415
for ( exercise_ind, spawn_res) in handles. into_iter ( ) . enumerate ( ) {
398
- write ! ( stdout, "\r Progress: {exercise_ind }/{n_exercises}" ) ?;
416
+ write ! ( stdout, "\r Progress: {checked_count }/{n_exercises}" ) ?;
399
417
stdout. flush ( ) ?;
400
418
401
- let Ok ( handle) = spawn_res else {
402
- return Ok ( AllExercisesCheck :: CheckedUntil ( exercise_ind) ) ;
403
- } ;
404
-
405
- let Ok ( success) = handle. join ( ) . unwrap ( ) else {
406
- return Ok ( AllExercisesCheck :: CheckedUntil ( exercise_ind) ) ;
407
- } ;
408
-
409
- if !success {
410
- return Ok ( AllExercisesCheck :: Pending ( exercise_ind) ) ;
411
- }
419
+ results[ exercise_ind] = spawn_res
420
+ . context ( "Spawn error" )
421
+ . and_then ( |handle| handle. join ( ) . unwrap ( ) )
422
+ . map_or_else (
423
+ |_| AllExercisesResult :: Error ,
424
+ |success| {
425
+ checked_count += 1 ;
426
+ if success {
427
+ AllExercisesResult :: Success
428
+ } else {
429
+ AllExercisesResult :: Failed
430
+ }
431
+ } ,
432
+ ) ;
412
433
}
413
434
414
- Ok :: < _ , io:: Error > ( AllExercisesCheck :: AllDone )
435
+ Ok :: < _ , io:: Error > ( ( checked_count , results ) )
415
436
} ) ?;
416
437
417
- let mut exercise_ind = match status {
418
- AllExercisesCheck :: Pending ( exercise_ind) => return Ok ( Some ( exercise_ind) ) ,
419
- AllExercisesCheck :: AllDone => return Ok ( None ) ,
420
- AllExercisesCheck :: CheckedUntil ( ind) => ind,
421
- } ;
422
-
423
- // We got an error while checking all exercises in parallel.
424
- // This could be because we exceeded the limit of open file descriptors.
425
- // Therefore, try to continue the check sequentially.
426
- for exercise in & self . exercises [ exercise_ind..] {
427
- write ! ( stdout, "\r Progress: {exercise_ind}/{n_exercises}" ) ?;
428
- stdout. flush ( ) ?;
429
-
430
- let success = exercise. run_exercise ( None , & self . cmd_runner ) ?;
431
- if !success {
432
- return Ok ( Some ( exercise_ind) ) ;
433
- }
438
+ // If we got an error while checking all exercises in parallel,
439
+ // it could be because we exceeded the limit of open file descriptors.
440
+ // Therefore, re-try those one at a time (i.e. sequentially).
441
+ results
442
+ . iter_mut ( )
443
+ . enumerate ( )
444
+ . filter ( |( _, result) | {
445
+ * * result == AllExercisesResult :: Pending || * * result == AllExercisesResult :: Error
446
+ } )
447
+ . try_for_each ( |( exercise_ind, result) | {
448
+ let exercise = self . exercises . get ( exercise_ind) . context ( BAD_INDEX_ERR ) ?;
449
+ * result = match exercise
450
+ . run_exercise ( None , & self . cmd_runner )
451
+ . context ( "Sequential retry" )
452
+ {
453
+ Ok ( true ) => AllExercisesResult :: Success ,
454
+ Ok ( false ) => AllExercisesResult :: Failed ,
455
+ Err ( err) => bail ! ( err) ,
456
+ } ;
457
+ checked_count += 1 ;
458
+ write ! ( stdout, "\r Progress: {checked_count}/{n_exercises}" ) ?;
459
+ stdout. flush ( ) ?;
460
+ Ok ( ( ) )
461
+ } ) ?;
434
462
435
- exercise_ind += 1 ;
436
- }
463
+ // Update the state of each exercise and return the first that failed
464
+ let first_fail = results
465
+ . iter ( )
466
+ . enumerate ( )
467
+ . filter_map ( |( exercise_ind, result) | {
468
+ match result {
469
+ AllExercisesResult :: Success => self
470
+ . set_status ( exercise_ind, true )
471
+ . map_or_else ( |err| Some ( Err ( err) ) , |_| None ) ,
472
+ AllExercisesResult :: Failed => self
473
+ . set_status ( exercise_ind, false )
474
+ . map_or_else ( |err| Some ( Err ( err) ) , |_| Some ( Ok ( exercise_ind) ) ) ,
475
+ // The sequential check done earlier will have converted all
476
+ // exercises to Success/Failed, or bailed, so those are unreachable
477
+ AllExercisesResult :: Pending | AllExercisesResult :: Error => unreachable ! ( ) ,
478
+ }
479
+ } )
480
+ . try_fold ( None :: < usize > , |current_min, index| {
481
+ match ( current_min, index) {
482
+ ( _, Err ( err) ) => Err ( err) ,
483
+ ( None , Ok ( index) ) => Ok ( Some ( index) ) ,
484
+ ( Some ( current_min) , Ok ( index) ) => Ok ( Some ( current_min. min ( index) ) ) ,
485
+ }
486
+ } ) ?;
487
+ self . write ( ) ?;
437
488
438
- Ok ( None )
489
+ Ok ( first_fail )
439
490
}
440
491
441
492
/// Mark the current exercise as done and move on to the next pending exercise if one exists.
@@ -467,9 +518,7 @@ impl AppState {
467
518
468
519
self . current_exercise_ind = pending_exercise_ind;
469
520
self . exercises [ pending_exercise_ind] . done = false ;
470
- // All exercises were marked as done.
471
- self . n_done -= 1 ;
472
- self . write ( ) ?;
521
+
473
522
return Ok ( ExercisesProgress :: NewPending ) ;
474
523
}
475
524
0 commit comments