Tencent / libpag

The official rendering library for PAG (Portable Animated Graphics) files that renders After Effects animations natively across multiple platforms.
https://pag.io
Other
4.97k stars 453 forks source link

PAGImageView 同步下载由于持有锁导致的 watchdog 问题 #2396

Open littlechaochao opened 2 months ago

littlechaochao commented 2 months ago

【版本信息】

4.3.57

【平台信息】

iOS 原生

【预期的表现】

子线程 PAGImageView 触发 flush 方法进行下载,主线程调用 PAGImageView 的 setHidden: 方法,不应该触发 watchdog

【实际的情况】

子线程 PAGImageView 触发 flush 方法会导致 PAG 文件的下载,最终会调用到下面的方法进行同步下载:

+ (PAGFile*)Load:(NSString*)path {
  if (path == nil) {
    return nil;
  }
  if ([PAGFileImpl IsNetWorkPath:path]) {
    NSData* cacheData = [PAGDiskCacheImpl ReadFile:path];
    if (cacheData == nil) {
      NSError* error = nil;
      cacheData = [NSData dataWithContentsOfURL:[NSURL URLWithString:path]
                                        options:NSDataReadingUncached
                                          error:&error]; // 这里进行同步的网络下载
      if (error == nil && cacheData != nil) {
        [PAGDiskCacheImpl WritFile:path data:cacheData];
      }
    }
    return [PAGFileImpl Load:cacheData.bytes size:cacheData.length path:path];
  }
  auto pagFile = pag::PAGFile::Load([path UTF8String]);
  if (pagFile == nullptr) {
    return nil;
  }
  return (PAGFile*)[PAGLayerImpl ToPAGLayer:pagFile];
}

如果网络下载比较耗时,PAGImageView 的 flush 方法一开始就持有了一个锁:

- (BOOL)flush {
  std::lock_guard<std::mutex> autoLock(imageViewLock); // 持有锁
  NSInteger frameIndex = [self currentFrame];
  if (self.memeoryCacheFinished) {
    if ([self checkPAGCompositionChanged] == NO) {
      if (self.currentFrameIndex != frameIndex) {
        UIImage* image = imagesMap[@(frameIndex)];
        if (image) {
          self.currentFrameIndex = frameIndex;
          self.currentUIImage = image;
          [self submitToImageView];
          return YES;
        }
      }
    }
  }
  if (self.currentFrameIndex == frameIndex) {
    return NO;
  }
  [self checkPAGCompositionChanged];
  CVPixelBufferRef pixelBuffer = self.memoryCacheEnabled ? [self getMemoryCacheCVPixelBuffer]
                                                         : [self getDiskCacheCVPixelBuffer];
  if (pixelBuffer == nil) {
    self.currentUIImage = nil;
    [self submitToImageView];
    return NO;
  }
  return [self updateImageViewFrom:pixelBuffer atIndex:frameIndex];
}

如果网络下载一直不返回,PAGImageView 的 flush 会一直持有这个锁,这样,如果主线程调用了其他方法,比如 setHidden:就会阻塞在这个锁上:

- (void)setHidden:(BOOL)hidden {
  [super setHidden:hidden];
  std::lock_guard<std::mutex> autoLock(imageViewLock); // 主线程被阻塞在这个锁上
  [self checkVisible];
}

如果主线程阻塞时间过长,就会导致 watchdog,应用被杀死

【Demo及附件】

peter100u commented 3 weeks ago

这个问题,我也遇到了,一直不知道怎么解决,大量的堵塞,导致ANR, 我们的产品在海外,网络太差