PHP - unlink抛出错误:资源暂时不可用

14

这里是一段代码:

public function uploadPhoto(){
    $filename = '../storage/temp/image.jpg';  
    file_put_contents($filename,file_get_contents('http://example.com/image.jpg'));
    $photoService->uploadPhoto($filename);
    echo("If file exists: ".file_exists($filename));
    unlink($filename);
}

我正在尝试实现以下几个操作:

  1. 从URL获取照片并将其保存到我的服务器的临时文件夹中。这部分工作正常,图像文件已创建,并在运行echo("If file exists: ".file_exists('../storage/temp/image.jpg'));时显示出If file exists: 1
  2. 将该文件传递给另一个函数,用于将该文件上传到Amazon S3存储桶中。文件将被存储在我的S3存储桶中。
  3. 删除存储在临时文件夹中的照片。此部分不起作用!我得到一个错误消息:

unlink(../storage/temp/image.jpg): Resource temporarily unavailable

如果我使用rename($filename,'../storage/temp/renimage.jpg');而不是unlink($filename);,我会收到一个错误消息:

rename(../storage/temp/image.jpg,../storage/temp/renimage.jpg): The process cannot access the file because it is being used by another process. (code:32)

如果我删除函数调用$photoService->uploadPhoto($filename);,一切都正常。

如果文件正在被其他进程使用,那么在进程完成并且文件不再被任何进程使用后,如何解除链接? 我不想使用计时器。

请帮忙!提前致谢。


1
作为一种旁注,您应该使用绝对文件路径而不是相对文件路径。在您的文件路径中,应使用 $_SERVER['DOCUMENT_ROOT'] 而不是 ../ - Martin
使用唯一的名称而不是 image.jpg,这样可能会更好,因为不同的请求可能尝试在同一个文件上进行操作。 - JustOnUnderMillions
1
@Martin 在 unlink 之后但在 file_exists 之前,而且谁知道 uploadPhoto 是做什么的。;-) 我只是粘贴了链接,这样原帖作者就可以阅读相关内容了。 - JustOnUnderMillions
@JustOnUnderMillions,是的。我在我的实际代码中使用了一个独特的名称生成器。上面给出的代码片段是为了在这里发布而使用的。 - Abhishek Reddy
uploadPhoto 的作用仅仅是创建 AWS 客户端并将照片上传到我的 s3 存储桶。@JustOnUnderMillions - Abhishek Reddy
显示剩余2条评论
5个回答

11

最简单的解决方法:

gc_collect_cycles();
unlink($file);

上传文件至Amazon S3后,可以直接在我的服务器上删除该文件。

参见:https://github.com/aws/aws-sdk-php/issues/841

GuzzleHttp\Stream 对象会保留资源句柄,直到其 __destruct 方法被调用。通常情况下,这意味着一旦流超出范围,资源就会被释放,但有时候,取决于 PHP 版本以及脚本是否填满了垃圾回收器的缓冲区,垃圾回收可能会被推迟。gc_collect_cycles 将强制执行垃圾回收并调用所有不可达流对象的 __destruct 方法。

:)


8

我刚刚遇到了类似的错误。

看起来你的$photoService因某种原因保留了该图像... 由于你没有分享$photoService的代码,我的建议是这样做(假设你不再需要$photoService):

[...]
echo("If file exists: ".file_exists($filename));
unset($photoService);
unlink($filename);
}
unset()方法会销毁指定的变量/对象,因此它无法“使用”(或执行任何操作)任何文件。

1
请注意,如果您正在运行Linux、MacOS或*BSD,则在删除之前无需关闭所有句柄。但是,如果您在MS Windows上运行php,则需要先关闭所有句柄。 - hanshenrik

4

我花了一两个小时思考这个问题,最终意识到“暂时不可用”确实意味着“暂时”。

在我的情况下,同时运行的PHP脚本访问文件,进行写入或读取。当unlink()进程的定时性较差时,整个事情就失败了。

解决方案非常简单:使用(通常不建议使用的)@来防止错误显示给用户(当然,还可以停止错误被打印),然后再尝试一次:

$gone = false;
for ($trial=0; $trial<10; $trial++) {
    if ($gone = @unlink($filename)) {
        break;
    }
    // Wait a short time
    usleep(250000);
    // Maybe a concurrent script has deleted the file in the meantime
    clearstatcache();
    if (!file_exists($filename)) {
        $gone = true;
        break;
    }
}
if (!$gone) {
    trigger_error('Warning: Could not delete file '.htmlspecialchars($filename), E_USER_WARNING);
}

在解决了这个问题并且再次尝试时,我还遇到了使用file_put_contents()时的“资源暂时不可用”问题。采用同样的解决方法后,现在一切都正常工作。
如果我足够聪明和/或以后删除失败,我会用ob_start()来替换@,这样错误消息就可以告诉我确切的错误。

2

我有同样的问题。S3客户端似乎不想在执行unlink之前进行解锁。如果你将内容提取到一个变量中,并将其设置为putObject数组中的'body':

$fileContent = file_get_contents($filepath);
$result = $s3->putObject(array(
    'Bucket'       => $bucket,
    'Key'          => $folderPath,
    'Body'         => $fileContent,
     //'SourceFile'   => $filepath,
    'ContentType'  => 'text/csv',
    'ACL'          => 'public-read'
));

请参考这个答案:如何在AWS S3助手上传文件后解锁文件?


-2

unlink方法返回布尔值,因此您可以构建一个循环,使用一些wait()和重试限制来等待您的进程完成。

此外,在unlink上加上"@",以隐藏访问错误。

如果重试计数达到,则抛出另一个错误/异常。


3
这些都是可怕的解决方案,旨在隐藏症状而不是解决问题。不要使用@错误抑制,等待将会占用您的服务器进程(因为PHP在等待时会循环使用CPU资源,而不是暂停处理)。 - Martin
@Martin,我同意。不建议使用@ - Abhishek Reddy
我听到这个笑了出来。如果有错误就压制它!哈哈! - Daniel Tate
当然,这并不可取,而且在更高版本的PHP中捕获这些警告的更好解决方案肯定有很多。不确定为什么你们还坚持这个特定的部分,哈哈。你可以尽情地笑 :) 很高兴看到我让某人的一天变得更好了。 - Anton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接