HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

File.deleteOnExit() を使用していたライブラリ

トラブルは得てして古い環境で起こる。以下のエントリ:
File.createTempFile() と File.deleteOnExit() のコンボ罠の続き - HHeLiBeXの日記 正道編
を書いたのもそんなトラブルがきっかけだが、原因の1つはアプリケーションそのものではない‥いや、(古いライブラリを使っているという点では)ある意味アプリケーションが原因。
何かというと、Commons FileUpload v1.0 に含まれる問題。v1.1 で解決されているのだが、そんな古いライブラリを使っているんです、はい‥
再現させるのは、作業的にはすごく簡単。(ただマシンへの負荷がかかるだけ)
準備するものは次のとおり。

検証のためのWebアプリは次のとおり。
まずCommons FileUploadを使用する部分のコード。ファイル名は「FileUploadTest10.java」としておく。

import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.DiskFileUpload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;

public class FileUploadTest10 {

    public static void parse(HttpServletRequest request) throws FileUploadException {
        DiskFileUpload fileUpload = new DiskFileUpload();
        List items = fileUpload.parseRequest(request);
        for (Iterator it = items.iterator(); it.hasNext();) {
            FileItem item = (FileItem) it.next();
            if (item.isFormField()) {
System.out.println(item.getFieldName() + ":" + item.isFormField() + "=" + item.getString());
            }
        }
    }

}

これを呼び出すコードを含むJSPファイル。ファイル名は「action.jsp」としておく。

<%@ page contentType="text/html" pageEncoding="UTF-8" %>
<%@ page session="false" %>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "DTD/xhtml11.dtd">
<%
response.setHeader("pragma","no-cache");
response.setHeader("Cache-Control","no-cache");
%>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
        <title>Commons FileUpload Test</title>
    </head>
    <body>
        <%
            FileUploadTest10.parse(request);
        %>
        <div>
            <a href="index.html">to index page</a>
        </div>
    </body>
</html>

このaction.jspを呼び出すためのHTMLソース。ファイル名は「index.html」としておく。

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Commons FileUpload Test - index page</title>
    </head>
    <body>
        <form name="testForm" action="action.jsp" method="POST" enctype="multipart/form-data">
            <input type="text" name="mytext" />
            <input type="file" name="myfile" />
            <input type="submit" />
        </form>
    </body>
</html>

最後に、「web.xml」。

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">

<web-app>
    <display-name>Commons FileUpload Test Application</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>


で、このWebアプリをコンパイルし、アプリケーションサーバーにデプロイする。
その後、数十万回ほど「index.html」⇒「action.jsp」というアクセスを繰り返す。指定するファイルは何でもいい。また適切なPOSTパラメータを渡せるならば、「action.jsp」を直接呼び出してもよい。
実際、手作業で数十万回なんてやってられないので、JMeterを使用した。(詳しいやり方は省略)


今回は、Eclipse 3.5+Sysdeo Tomcat PluginでTomcatを起動。
30万回アクセスした場合の、Tomcatプロセスの使用メモリ量(起動直後は27MB程度)

  • commons-fileupload-1.0.jar を使用
    • ⇒356MB程度まで増加
  • commons-fileupload-1.1.jar を使用 (Commons I/Oが別途必要)
    • ⇒84MB程度で打ち止め(15万回辺りから一定)

ライブラリを置き換えられない場合は、最低限必要なコードを修正して置き換えるしかない。Commons FileUpload v1.0 のソースパッケージをダウンロードし、ソース「src/java/org/apache/commons/fileupload/DefaultFileItem.java」を見てみると、619〜621行目に次のようなコードがある。

        File f = new File(tempDir, fileName);
        f.deleteOnExit();
        return f;

この「f.deleteOnExit();」の行をコメントアウトしてコンパイルし、JARファイルを更新する。サーバーを長期間停止させない場合は「exit時に削除する」なんて意味がないので、大きな影響はない。同じソースの少し上を見るとわかるが、ファイルの削除処理がfinalize()メソッドの呼び出しに依存している。この点が少し気にはなるが、Java VMクラッシュ/OutOfMemoryErrorによるダウンに比べれば‥

ちなみに、Commons FileUpload v1.1 では、上記の「FileUploadTest10.java」で使用しているクラスは大部分が非推奨となっている。
v1.1 での推奨コードの例は次のとおり。

import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;

public class FileUploadTest11 {

    public static void parse(HttpServletRequest request) throws FileUploadException {
        FileItemFactory factory = new DiskFileItemFactory();
        FileUpload fileUpload = new ServletFileUpload(factory);
        List items = fileUpload.parseRequest(new ServletRequestContext(request));
        for (Iterator it = items.iterator(); it.hasNext();) {
            FileItem item = (FileItem) it.next();
            if (item.isFormField()) {
System.out.println(item.getFieldName() + ":" + item.isFormField() + "=" + item.getString());
            }
        }
    }

}