4

I noticed that under certain circumstances Progress.fractionCompleted exceeds 1.0 or even resets back to 0. I first noticed it while using the MultipeerConnectivity framework, where I built a progress tree with Progress instances returned from MCSession.sendResource.

The code below reproduces the strange behavior. Simply copy paste.

let childUnitCounts: [(initialCompletedUnitCount: Int64, totalUnitCount: Int64)] = [
    (9855, 34375),
    (10950, 19110),
    (9855, 37511),
    (9855, 18799),
    (9855, 42705),
    (10950, 19021), // When commenting out this line or setting the initial unit count to 0, things work as expected.
]

let parent = Progress(totalUnitCount: Int64(childUnitCounts.count))

let children: [Progress] = childUnitCounts.map { unitCount in
    let child = Progress(totalUnitCount: unitCount.totalUnitCount)
    child.completedUnitCount = unitCount.initialCompletedUnitCount

    parent.addChild(child, withPendingUnitCount: 1)

    return child
}

print(parent)

for (i, state) in childUnitCounts.enumerated() {
    children[i].completedUnitCount = state.totalUnitCount
    print(parent)
}

The above produces the following output:

<NSProgress: 0x600000f6da40> : Parent: 0x0 (portion: 0) / Fraction completed: 0.4088 / Completed: 0 of 6  
  <NSProgress: 0x600000f6dc20> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.2627 / Completed: 9855 of 37511  
  <NSProgress: 0x600000f6db80> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5730 / Completed: 10950 of 19110  
  <NSProgress: 0x600000f6dae0> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.2867 / Completed: 9855 of 34375  
  <NSProgress: 0x600000f6de00> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021  
  <NSProgress: 0x600000f6dd60> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.2308 / Completed: 9855 of 42705  
  <NSProgress: 0x600000f6dcc0> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5242 / Completed: 9855 of 18799  
<NSProgress: 0x600000f6da40> : Parent: 0x0 (portion: 0) / Fraction completed: 0.5277 / Completed: 1 of 6  
  <NSProgress: 0x600000f6dc20> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.2627 / Completed: 9855 of 37511  
  <NSProgress: 0x600000f6db80> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5730 / Completed: 10950 of 19110  
  <NSProgress: 0x600000f6de00> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021  
  <NSProgress: 0x600000f6dd60> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.2308 / Completed: 9855 of 42705  
  <NSProgress: 0x600000f6dcc0> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5242 / Completed: 9855 of 18799  
<NSProgress: 0x600000f6da40> : Parent: 0x0 (portion: 0) / Fraction completed: 0.5989 / Completed: 2 of 6  
  <NSProgress: 0x600000f6dc20> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.2627 / Completed: 9855 of 37511  
  <NSProgress: 0x600000f6de00> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021  
  <NSProgress: 0x600000f6dd60> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.2308 / Completed: 9855 of 42705  
  <NSProgress: 0x600000f6dcc0> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5242 / Completed: 9855 of 18799  
<NSProgress: 0x600000f6da40> : Parent: 0x0 (portion: 0) / Fraction completed: 0.8094 / Completed: 3 of 6  
  <NSProgress: 0x600000f6de00> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021  
  <NSProgress: 0x600000f6dd60> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.2308 / Completed: 9855 of 42705  
  <NSProgress: 0x600000f6dcc0> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5242 / Completed: 9855 of 18799  
<NSProgress: 0x600000f6da40> : Parent: 0x0 (portion: 0) / Fraction completed: 0.8886 / Completed: 4 of 6  
  <NSProgress: 0x600000f6de00> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021  
  <NSProgress: 0x600000f6dd60> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.2308 / Completed: 9855 of 42705  
<NSProgress: 0x600000f6da40> : Parent: 0x0 (portion: 0) / Fraction completed: 1.0169 / Completed: 5 of 6  
  <NSProgress: 0x600000f6de00> : Parent: 0x600000f6da40 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021  
<NSProgress: 0x600000f6da40> : Parent: 0x0 (portion: 0) / Fraction completed: 1.2795 / Completed: 6 of 6  

The last line should obviously end with Fraction completed: 1.0. When commenting out the last element of childUnitCounts or setting the initial unit count to 0, it works properly.

I think this is a bug but before reporting it as such, I want to make sure I'm not missing something.

bcause
  • 1,303
  • 12
  • 29
  • "Don't update the completedUnitCount in a tight loop.", guess I missed that one, thanks! I'll dig into that a little more. As for the use of `fractionCompleted` (since it's a float, not because of its value!), that part I knew but used it here for the sake of emphasizing the issue. The problem is unfortunately much deeper, depending on the size of the progress tree, `fractionCompleted` sometimes jumps around, say from 0.9 to 0.1 and then to 1.3. I understand that `fractionCompleted` shouldn't be used for checking completion, but it is supposed to be used for user facing text and controls! – bcause Nov 22 '19 at 20:01
  • Oh, there you go. Was wondering what's happening with the disappearing answers. @matt Know that I appreciate your involvement! I still "hope" that I'm missing something on my side before "having to" conclude that this is a bug. – bcause Nov 22 '19 at 20:03
  • True. When leaving out (or using 0 as) the intermediate values, things work properly, regardless of the number of children. I also tried setting `completedUnitCount` asynchronously, but the results are the same. I think `not setting completedUnitCount in a tight loop` is mainly for performance reasons (KVO). – bcause Nov 22 '19 at 20:45
  • 1
    This behavior is iOS 13 only! Totally different on iOS 12. I’m ready to declare this a bug. – matt Nov 23 '19 at 06:20
  • That's it. I can confirm that `fractionCompleted` has correct values on iOS 12 and iOS 11. Feel free to add this as an answer. – bcause Nov 23 '19 at 19:07
  • Did so. Do you want to report this? – matt Nov 23 '19 at 19:16
  • I'll let you do the honors if you'd like. You conducted a couple of valid experiments that lead to this conclusion. – bcause Nov 23 '19 at 19:26
  • 1
    OK this is feedback FB7463813. – matt Nov 23 '19 at 19:42

1 Answers1

1

In an attempt to remove sources of doubt, I've gone through various exercises such as adding a delay before each change in a child progress, twiddling various numbers, and even translating the whole thing into Objective-C to make sure this isn't some sort of Swift numerics issue:

int64_t totals[] = {34375, 19110, 37511, 18799, 42705, 19021};
int64_t initials[] = {9855, 10950, 9855, 9855, 9855, 10950};
int sz = 6;
NSProgress* parent = [NSProgress progressWithTotalUnitCount:sz];
NSMutableArray* children = [NSMutableArray array];
for (int i = 0; i<sz; i++) {
    NSProgress* child = [NSProgress progressWithTotalUnitCount:totals[i]];
    child.completedUnitCount = initials[i];
    [children addObject:child];
    [parent addChild:child withPendingUnitCount:1];
}
for (int i = 0; i<sz; i++) {
    NSProgress* child = children[i];
    child.completedUnitCount = totals[i];
}
NSLog(@"%@", parent);

My conclusions are:

  • Even in Objective-C, the above code ends up with a parent fraction completed of 1.2795.

  • If sz is 5 or less, the parent fraction completed ends up as 1. The issue seems to arise only when the child count rises above a certain number.

  • Changing the timing by adding a delay makes no difference (code not shown here).

  • The very same code run on iOS 12 always gets a parent fraction completed of 1!

So while it takes a great deal to persuade me that Apple could have made such a mistake, I am persuaded. You've found a really nasty bug in iOS 13 Foundation.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I just hope this is not a deep rooted problem with floating point numbers, because changing the unit count values produces other results (but also above 1.0)! – bcause Nov 23 '19 at 19:29