/** -*- tab-width: 4 -*-
* This file is part of Erjang - A JVM-based Erlang VM
*
* Copyright (c) 2011 by Trifork
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package erjang;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.jar.JarOutputStream;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import erjang.beam.BeamLoader;
import erjang.beam.Compiler;
import erjang.beam.RamClassRepo;
public class ErjangCodeCache {
static final Logger log = Logger.getLogger("erjang.beam.cache");
// Config:
static final String ERJ_CACHE_DIR;
static final boolean useAsyncPersisting;
static final boolean useSyncPersisting;
static final Persister persister;
static {
String cacheDir = System.getenv("ERJ_CACHE_DIR");
if (cacheDir == null) cacheDir = System.getProperty("user.home");
ERJ_CACHE_DIR = cacheDir;
String mode = System.getProperty("erjang.codecache.mode");
if ("async".equals(mode)) {
useAsyncPersisting = true;
useSyncPersisting = false;
} else if ("sync".equals(mode)) {
useAsyncPersisting = false;
useSyncPersisting = true;
} else if ("off".equals(mode)) {
useAsyncPersisting = false;
useSyncPersisting = false;
} else {
// TODO: Warn?
// Default to 'async':
useAsyncPersisting = true;
useSyncPersisting = false;
} // Other values which might make sense: 'read-only', 'existing-only'
if (useAsyncPersisting) {
persister = new Persister();
Thread t = new Thread(persister, "Erjang Code Cache Persister");
t.setDaemon(true);
t.setPriority(Thread.MIN_PRIORITY);
t.start();
} else persister = null;
}
private static Map<String, RamClassRepo> cache = Collections.synchronizedMap(new HashMap<String, RamClassRepo>());
public static EModuleClassLoader getModuleClassLoader(String moduleName, EBinary beam_data, BeamLoader beam_parser) throws IOException {
long crc = beam_data.crc();
// crc ^= BIFUtil.all_bif_hash();
File jarFile = new File(erjdir(), moduleJarFileName(moduleName, crc));
if (jarFile.exists()) {
return new EModuleClassLoader(jarFile.toURI().toURL());
}
RamClassRepo repo = new RamClassRepo();
try {
Compiler.compile(beam_parser.load(beam_data.getByteArray()), repo);
repo.close();
cache.put(moduleName, repo);
if (useAsyncPersisting) persister.enqueue(jarFile, repo);
else if (useSyncPersisting) persister.persist(jarFile, repo);
} finally {
try {repo.close();
// jarFile.delete();
} catch (Exception e) {}
}
return new EModuleClassLoader(jarFile.toURI().toURL(), repo);
}
static File erjdir() throws IOException {
File home = ERT.newFile(ERJ_CACHE_DIR);
File dir = new File(home, ".erjang");
if (!dir.exists()) {
if (!dir.mkdirs())
throw new IOException("cannot create " + dir);
} else if (!dir.canWrite()) {
throw new IOException("cannot write to " + dir);
}
return dir;
}
public static String moduleJarFileName(String moduleName, long crc) {
return moduleFileName(moduleName, crc, "jar");
}
/*
static String moduleJarBackupFileName(String moduleName, long crc) {
return moduleFileName(moduleName, crc, "ja#");
}
*/
static String moduleFileName(String moduleName, long crc, String extension) {
return mangle(moduleName)
+ "-" + Long.toHexString(crc)
+ "." + extension;
}
/** Mangle string so that the result contains only [a-z0-9_$]. */
static String mangle(String s) {
// TODO: Faster handling of the normal case.
StringBuffer sb = new StringBuffer();
for (int i=0; i<s.length(); i++) {
char c = s.charAt(i);
if (('a' <= c && c <= 'z') ||
('A' <= c && c <= 'Z') ||
('0' <= c && c <= '9') ||
c == '-' ||
c == '.' ||
c == '_')
sb.append(c);
else
sb.append('$').append(Integer.toHexString(c)).append('$');
}
return sb.toString();
}
static class PersistRequest { // Just a Pair<File,RamClassRepo>, really.
final File file;
final RamClassRepo repo;
public PersistRequest(File file, RamClassRepo repo) {
this.file = file;
this.repo = repo;
}
}
static class Persister implements Runnable {
final Queue<PersistRequest> queue = new LinkedList<PersistRequest>();
public void run() {
while (true) {
PersistRequest request;
synchronized (queue) {
while ((request = queue.poll()) == null) {
try { queue.wait(); }
catch (InterruptedException ie) {}
}
}
persist(request.file, request.repo);
}
}
void enqueue(File file, RamClassRepo repo) {
synchronized (queue) {
queue.add(new PersistRequest(file, repo));
queue.notify();
}
}
static void persist(File file, RamClassRepo repo) {
try {
File tmpFile = File.createTempFile(file.getName(), "tmp",
file.getParentFile());
JarOutputStream jo = new JarOutputStream(new FileOutputStream(tmpFile));
jo.setLevel(0);
for (Map.Entry<String,byte[]> e : repo.entrySet()) {
String classFilename = e.getKey() + ".class";
byte[] classContents = e.getValue();
jo.putNextEntry(new ZipEntry(classFilename));
jo.write(classContents);
jo.closeEntry();
}
jo.close();
tmpFile.renameTo(file);
} catch (IOException ioe) {
log.warning("Warning: Failed to store cached module in "+file);
}
}
}
}