Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash when cancelAll by SDWebImageManager. #809

Closed
maeji opened this issue Jul 16, 2014 · 35 comments
Closed

Crash when cancelAll by SDWebImageManager. #809

maeji opened this issue Jul 16, 2014 · 35 comments
Labels
Milestone

Comments

@maeji
Copy link

maeji commented Jul 16, 2014

In tableView, I used SDWebImageManager. When I was try to cancel all download, My app was crashed at "self.cancelBlock = nil;" in SDWebImageManager.m (317 line).

I think that is released object problem.

SDWebImageCombinedOperation has cancelBlock. And It set like that,

operation.cancelBlock = ^{
     [subOperation cancel];
                @synchronized (self.runningOperations) {
                    [self.runningOperations removeObject:weakOperation];
                }
};

Also it was copied by below function in code.

- (void)setCancelBlock:(void (^)())cancelBlock {
    if (self.isCancelled) {
        if (cancelBlock) cancelBlock();
    }
    else {
        _cancelBlock = [cancelBlock copy];
    }
}

It was crashed at "self.cancelBlock = nil;" in cancel function in SDWebImageCombinedOperation. In my thought that was SDWebImageCombinedOperation was released before set nil.

- (void)cancel {
    self.cancelled = YES;
    if (self.cacheOperation) {
        [self.cacheOperation cancel];
        self.cacheOperation = nil;
    }
    if (self.cancelBlock) {
        self.cancelBlock();
        self.cancelBlock = nil;
    }
}

When I removed "self.cancleBlock = nil;". It was working perfect. Is it right?

@bpoplauschi
Copy link
Member

Mi @maeji. We will look into this crash soon.

@bpoplauschi bpoplauschi added this to the 4.0.0 milestone Jul 16, 2014
@rromanchuk
Copy link
Contributor

can confirm crashing when canceling prefetcher.

Application Specific Information:
objc_msgSend() selector name: setCancelBlock:

Thread 0 Crashed:
0   libobjc.A.dylib                      0x3861f636 objc_msgSend + 22
1   CoreFoundation                       0x2ddf2a95 -[NSArray makeObjectsPerformSelector:] + 178
2   Frontback β                          0x0018165d -[SDWebImageManager cancelAll] (SDWebImageManager.m:286)
3   Frontback β                          0x001825d7 -[SDWebImagePrefetcher cancelPrefetching] (SDWebImagePrefetcher.m:135)

If you guys need help testing with a real sample size, I can help by pushing out HEAD before SDWebImage cuts a release. Right now there are two show stopping crashes that took less than a minute to discover in a real world environment.

@bpoplauschi
Copy link
Member

@rromanchuk I have tried to reproduce this crash, but I doesn't occur to me. Could you help me with that? Do you have a scenario that works all the time?

@bpoplauschi
Copy link
Member

Also, could you please check using the latest HEAD for the other crash (#808), it should be fixed now.

@rromanchuk
Copy link
Contributor

Yes #808 is fine, this bug is hard to reproduce, and I only see it frequently with 400+ active users, it's still my number one crash at the moment though.

I reduced a majority of the crashes by removing calls to - (void)cancelPrefetching; from SDWebImagePrefetcher

I will try and make the crash reproducible by prefetching and canceling but you can also see prefetching is canceled when given a new set of urls

- (void)prefetchURLs:(NSArray *)urls progress:(SDWebImagePrefetcherProgressBlock)progressBlock completed:(SDWebImagePrefetcherCompletionBlock)completionBlock {
    [self cancelPrefetching]; // Prevent duplicate prefetch request
    self.startedTime = CFAbsoluteTimeGetCurrent();
    self.prefetchURLs = urls;
    self.completionBlock = completionBlock;
    self.progressBlock = progressBlock;

    // Starts prefetching from the very first image on the list with the max allowed concurrency
    NSUInteger listCount = self.prefetchURLs.count;
    for (NSUInteger i = 0; i < self.maxConcurrentDownloads && self.requestedCount < listCount; i++) {
        [self startPrefetchingAtIndex:i];
    }
}

@rromanchuk
Copy link
Contributor

My guess is the best way to make it crash is by having active prefetching URLS, and interrupt them before they have finished

It looks like I fixed the crash by changing self.cancelBlock = nil to _cancelBlock = nil as the crash is happening because self does not respond to the setter, something must be releasing too early. This is a similar finding that @maeji is reporting

- (void)cancel {
    self.cancelled = YES;
    if (self.cacheOperation) {
        [self.cacheOperation cancel];
        self.cacheOperation = nil;
    }
    if (self.cancelBlock) {
        self.cancelBlock();
        _cancelBlock = nil; // <------ fixes the crash
    }
}

@bpoplauschi
Copy link
Member

I think I found the issue. self.cancelBlock = nil; will trigger a call to our custom setter setCancelBlock:. Looking at that code

- (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock {
    if (self.isCancelled) {
        if (cancelBlock) cancelBlock();
    }
    else {
        _cancelBlock = [cancelBlock copy];
    }
}
  • the check of self.isCancelled passes since we just had self.cancelled = YES;
  • then if (cancelBlock) will not pass, as the call to the setter was made using nil as param
  • and then we exit, without ever setting _cancelBlock to nil
  • so if after this, an attempt to use self.cancelBlock will lead to a crash.

@bpoplauschi
Copy link
Member

@rromanchuk Thanks for the support. Could you also check my latest fix (28109c4) against this crash? It shouldn't crash anymore (your clue that _cancelBlock = nil; got me on the right track). As soon as I get the approval, I will release that patch.

@rromanchuk
Copy link
Contributor

@bpoplauschi thanks! Let me rebuild with this commit, looks good though

@bpoplauschi
Copy link
Member

@rromanchuk did you get a chance to try this out? I want to release the patch asap.

@rromanchuk
Copy link
Contributor

@bpoplauschi sending a release out now, should know soon

@rromanchuk
Copy link
Contributor

looks like i'm still getting it :(

Thread 0 Crashed:
0 libobjc.A.dylib 0x3a262626 objc_msgSend + 6
1 CoreFoundation 0x2fa33ad5 -[NSArray makeObjectsPerformSelector:] + 178
2 Frontback β 0x001f2ce9 -SDWebImageManager cancelAll
3 Frontback β 0x001f3c7b -SDWebImagePrefetcher cancelPrefetching
4 Frontback β 0x001f3afd -SDWebImagePrefetcher prefetchURLs:progress:completed:
5 Frontback β 0x001f3ac1 -SDWebImagePrefetcher prefetchURLs:

@bpoplauschi
Copy link
Member

Until we can figure out the exact cause, made a temp fix (e95224b) with the suggested _cancelBlock = nil

@bpoplauschi
Copy link
Member

3.7.1 just got out. Could you please check and see if this crash still reproduces or is fixed as it should be?

@maeji
Copy link
Author

maeji commented Jul 24, 2014

2014-07-24 4 08 36
Still reproduce that crash.

@bpoplauschi
Copy link
Member

What version are you using? I'm asking because that code is a bit different now

@rromanchuk
Copy link
Contributor

Bogdan, things seem OK with the patch, will let you know if I see anything

Sent from my iPhone

On Jul 24, 2014, at 12:12 AM, Bogdan Poplauschi notifications@github.com wrote:

What version are you using? I'm asking because that code is a bit different now


Reply to this email directly or view it on GitHub.

@bpoplauschi
Copy link
Member

Great, thanks.

@farhanumer
Copy link

I just got the same crash with 3.7.1
screenshot

@justjimmy
Copy link
Contributor

I seem to occasionally have this issue too.

When I do:

[[SDWebImageManager sharedManager] cancelAll];

I seem to get this exception:

* Terminating app due to uncaught exception 'NSGenericException', reason: '* Collection <__NSArrayM: 0x17024c390> was mutated while being enumerated.'

@justjimmy
Copy link
Contributor

screen shot 2014-07-30 at 5 21 22 pm

@justjimmy
Copy link
Contributor

this seems to fix it for me.

Pull request: #838

toshinarin pushed a commit to sumally/SDWebImage that referenced this issue Aug 4, 2014
@DmitrijMaz
Copy link

same thing happens for cacheOperation setter.

  • (void)cancel {
    self.cancelled = YES;
    if (self.cacheOperation) {
    [self.cacheOperation cancel];
    self.cacheOperation = nil; // <---- crash here
    }
    if (self.cancelBlock) {
    self.cancelBlock();

    // TODO: this is a temporary fix to #809.
    // Until we can figure the exact cause of the crash, going with the ivar instead of the setter
    

    // self.cancelBlock = nil;
    _cancelBlock = nil;
    }
    }

@matteogobbi
Copy link

It continue to crash.

@dmiedema
Copy link

dmiedema commented Sep 2, 2014

I can consistently get this to crash by prefetching different sets of images very quickly.

I created an example project to show the crash with instructions in the README on how to trigger the crash also. It currently crashes for me 100% of the time

https://github.com/dmiedema/SDWebImageCrashExample

@justjimmy
Copy link
Contributor

I had a pull request that fixed it for me. It was merged in but you have to grab it using the pod with a specific commit. It's not in 3.7.1 but will be in 3.7.2. Can you try in and see?—
Sent from Mailbox

On Wed, Sep 3, 2014 at 3:54 AM, Daniel Miedema notifications@github.com
wrote:

I can consistently get this to crash by prefetching different sets of images very quickly.
I created an example project to show the crash with instructions in the README on how to trigger the crash also. It currently crashes for me 100% of the time

https://github.com/dmiedema/SDWebImageCrashExample

Reply to this email directly or view it on GitHub:
#809 (comment)

@dmiedema
Copy link

dmiedema commented Sep 2, 2014

@justjimmy looks like referencing that commit fixes the issue in the example project.

Thanks

For anyone curious and reading this, until its put into a new release putting

pod 'SDWebImage', :git => 'https://github.com/rs/SDWebImage', :commit => '3587b8d1e0f51710dfd1cec0c3a723048f7473f4'

in your Podfile will fix this particular issue.

@justjimmy
Copy link
Contributor

@dmiedema awesome. I think it'll be part of 3.7.2 whenever that is ready.

@rromanchuk
Copy link
Contributor

@dmiedema any reason you are not using pod 'SDWebImage', :head? I recently dropped down to 3.6 because other issues were popping up, besides the cancel all issue, but haven't verified it's due to SDWebImage, needs much more investigation. Just can't afford to upgrade until it has served at least million images on production. Swamped with ios8 stuff right now, come back to it and hopefully help out when i have more time.

@dmiedema
Copy link

dmiedema commented Sep 3, 2014

@rromanchuk theres no particular reason im not using :head. I personally tend to prefer releases since i feel & hope theyre more tested & verified to be working.

Currently we've worked around the cancel all bug. i just didnt know it was resolved so i wanted to make a test project where it was reproducable.

Its our most common crash for now, however with the work around & known fix it should become a non-issue. At least for us.

devedup pushed a commit to FilmFlexMovies/SDWebImage that referenced this issue Sep 10, 2014
devedup pushed a commit to FilmFlexMovies/SDWebImage that referenced this issue Sep 10, 2014
@rsanchezsaez
Copy link

Commit b59c2bb doesn't fix this. I just got it:

screen shot 2014-10-01 at 17 04 42

@drumnkyle
Copy link

Any idea when 3.1.2 is going to come out with this fix? We are trying to push an update to the SlideShare app tomorrow and this in our top 3 crashes.

I think I'm just going to change our pod to reference the specific commit. Is 3587b8d still the right one to use to fix all these issues, including 798?

@timkoma
Copy link

timkoma commented Jan 30, 2015

I'm using 3.7.1 and I'm still getting crash.
Why it was closed ?

@mythodeia
Copy link
Contributor

@timkoma this issues is not closed
you got confused of the reference of the other issue. this is still open

@timkoma
Copy link

timkoma commented Jan 31, 2015

I think that in my case it is a concurency problem. I called [[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:url];
several times from background threads.
I wasn't aware of the cancelAll at the beginning of the prefetchURLs. Also there is no warning in the doc that I have to call prefetchURLs from main thread. Anyway I think that prefetchURLs should work in the background (at least it will be very nice feature).

timkoma referenced this issue in timkoma/SDWebImage Feb 3, 2015
Conflicts:
	SDWebImage/SDWebImageManager.m
@bpoplauschi bpoplauschi modified the milestones: 3.7.2, 4.0.0 Mar 19, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests