/** * This file is part of Erjang - A JVM-based Erlang VM * * Copyright (c) 2009 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.m.ets; import java.lang.ref.WeakReference; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; import kilim.Pausable; import com.trifork.clj_ds.APersistentMap; import com.trifork.clj_ds.IMapEntry; import com.trifork.clj_ds.IPersistentMap; import com.trifork.clj_ds.ISeq; import com.trifork.clj_ds.PersistentTreeMap; import erjang.EAtom; import erjang.EInteger; import erjang.EInternalPID; import erjang.EObject; import erjang.EProc; import erjang.ERT; import erjang.ESeq; import erjang.ETuple; import erjang.ETuple2; import erjang.ETuple3; import erjang.ErlangError; import erjang.ExitHook; import erjang.NotImplemented; /** * Abstract super class for an ETS table. We implement ETS by using * Clojure's persistent data types. * * set: PersistentHashMap [key, value] * ordered_set: PersistentTreeMap [key, value] * bag PersistentHashMap [key, PersistentSet [value]] * duplicate_bag: PersistentHashMap [key, PersistentList [value]] * */ abstract class ETable implements ExitHook { public static final EAtom am_stm = EAtom.intern("stm"); protected WeakReference<EProc> owner; protected EAtom access; protected final int keypos1; protected EInternalPID heirPID; protected EObject heirData; protected final EInteger tid; protected final EAtom aname; protected final boolean is_named; protected final EAtom type; protected final APersistentMap<EObject,Object> empty; private AtomicReference<IPersistentMap<EObject, Object>> mapRef; private boolean is_fixed; ETable(EProc owner, EAtom type, EInteger tid, EAtom aname, EAtom access, int keypos, boolean is_named, EInternalPID heir_pid, EObject heir_data, APersistentMap<EObject,Object> map) { this.type = type; this.is_named = is_named; this.owner = new WeakReference<EProc>(owner); this.tid = tid; this.aname = aname; this.access = access; this.keypos1 = keypos; this.heirPID = heir_pid; this.heirData = heir_data; try { this.mapRef = new AtomicReference<IPersistentMap<EObject,Object>>(map); } catch (Exception e) { throw new ErlangError(am_stm); } empty = map; owner.add_exit_hook(this); } /** * Allocate table with specified configuration */ public static ETable allocate(EProc proc, EInteger tid, EAtom aname, EAtom type, EAtom access, int keypos, boolean write_concurrency, boolean is_named, EInternalPID heir_pid, EObject heir_data) { if (type == Native.am_set || type == Native.am_ordered_set) { return new ETableSet(proc, type, tid, aname, access, keypos, write_concurrency, is_named, heir_pid, heir_data); } if (type == Native.am_bag || type == Native.am_duplicate_bag) { return new ETableBag(proc, type, tid, aname, access, keypos, write_concurrency, is_named, heir_pid, heir_data); } throw new NotImplemented("ets type=" + type + "; access=" + access + "; keypos=" + keypos + "; write_concurrency=" + write_concurrency + "; heir_pid=" + heir_pid); } /** * @param caller * @param access * @return */ public final boolean allow_access(EProc caller, boolean write_access) { if (access == Native.am_protected) { if (write_access) { return (caller == owner.get()); } else { return true; } } else if (access == Native.am_public) { return true; } else if (access == Native.am_private) { return (caller == owner.get()); } else { throw new InternalError("invalid access mode"); } } EInternalPID owner_pid() { EProc o = owner.get(); if (o == null) throw ERT.badarg(); return o.self_handle(); } ESeq info() { ESeq rep = ERT.NIL; ETable table = this; rep = rep.cons(new ETuple2(Native.am_owner, table.owner_pid())); rep = rep.cons(new ETuple2(Native.am_named_table, ERT.box(table.is_named))); rep = rep.cons(new ETuple2(Native.am_name, table.aname)); if (table.heirPID != null) rep = rep.cons(new ETuple2(Native.am_heir, table.heirPID)); else rep = rep.cons(new ETuple2(Native.am_heir, Native.am_none)); rep = rep.cons(new ETuple2(Native.am_size, ERT.box(table.size()))); rep = rep.cons(new ETuple2(Native.am_node, ERT.getLocalNode().node())); rep = rep.cons(new ETuple2(Native.am_type, table.type)); rep = rep.cons(new ETuple2(Native.am_keypos, ERT.box(table.keypos1))); rep = rep.cons(new ETuple2(Native.am_protection, table.access)); rep = rep.cons(new ETuple2(Native.am_fixed, ERT.box(is_fixed))); return rep; } public void on_exit(EInternalPID dyingPID) throws Pausable { if (dyingPID == owner_pid()) { EInternalPID heirPID = this.heirPID; if (heirPID != null && heirPID != dyingPID && transfer_ownership_to(heirPID, this.heirData == null ? ERT.NIL : this.heirData)) { // Transfered } else { //System.err.println("received exit from owner "+dyingPID+" => delete"); delete(); } } else { Native.log.warning("table "+aname+" ("+tid+") received exit from unrelated "+dyingPID); } } public boolean transfer_ownership_to(EInternalPID new_owner, EObject transfer_data) throws Pausable { EInternalPID former_owner = owner_pid(); EProc new_owner_task; if ((new_owner_task = new_owner.task()) != null) { //System.err.println("received exit from owner "+former_owner // +" => transfer to "+new_owner_task); ETuple msg = ETuple.make(EAtom.intern("ETS-TRANSFER"), this.is_named ? this.aname : this.tid, former_owner, transfer_data); WeakReference<EProc> former_owner_ref = this.owner; this.owner = new WeakReference<EProc>(new_owner_task); if (new_owner_task.add_exit_hook(this)) { // OK - transfered new_owner.send(former_owner, msg); return true; } else { // The process is done and did not accept the hook // Roll back: this.owner = former_owner_ref; } } return false; } void delete() { EInternalPID p = owner_pid(); if (p != null) p.remove_exit_hook(this); Native.tid_to_table.remove(tid); if (is_named) { Native.name_to_tid.remove(aname); } } abstract int size(); /** utility for subclasses */ EObject get_key(ETuple value) { if (keypos1 > value.arity()) { // TODO: return full list of args throw ERT.badarg(ERT.NIL.cons(value)); } return value.elm(keypos1); } IPersistentMap<EObject,Object> deref() { return mapRef.get(); } abstract class WithMap<T> implements Callable<T> { private IPersistentMap<EObject,Object> orig; @Override public final T call() { return run(orig = mapRef.get()); } protected abstract T run(IPersistentMap<EObject,Object> map); protected void set(IPersistentMap<EObject,Object> map) { mapRef.compareAndSet(orig, map); } } <X> X in_tx(WithMap<X> run) { try { synchronized (ETable.this) { return run.call(); } } catch (ErlangError e) { throw e; } catch (Exception e) { e.printStackTrace(); // STM Failure throw new ErlangError(am_stm); } } protected abstract void insert_one(ETuple value); protected abstract void insert_many(ESeq values); protected abstract boolean insert_new_one(ETuple value); protected abstract boolean insert_new_many(ESeq values); protected abstract ESeq lookup(EObject key); protected abstract ESeq match(EPattern matcher); protected abstract ESeq match_object(EPattern matcher); protected abstract void delete(EObject key); protected abstract EInteger select_delete(EMatchSpec matcher); protected void delete_all_objects() { in_tx(new WithMap<Object>() { @Override protected Object run(IPersistentMap<EObject,Object> map) { set(empty); return null; } }); } protected abstract EObject first(); protected EObject next(EObject from) { IPersistentMap<EObject,Object> map = deref(); if (map instanceof PersistentTreeMap) { PersistentTreeMap<EObject,Object> ptm = (PersistentTreeMap<EObject,Object>) map; @SuppressWarnings("unchecked") ISeq<IMapEntry<EObject,Object>> seq = ptm.seqFrom(from, true); if (seq == null) return Native.am_$end_of_table; seq = seq.next(); if (seq == null) return Native.am_$end_of_table; IMapEntry<EObject,Object> ent = (IMapEntry<EObject,Object>) seq.first(); if (ent == null) return Native.am_$end_of_table; return (EObject) ent.getKey(); } else { for (ISeq<IMapEntry<EObject,Object>> seq = map.seq();seq != null;seq = seq.next()) { IMapEntry<EObject,Object> ent = (IMapEntry<EObject,Object>) seq.first(); if (ent == null) return Native.am_$end_of_table; EObject key = (EObject) ent.getKey(); if (key.equalsExactly(from)) { seq = seq.next(); if (seq == null) return Native.am_$end_of_table; ent = (IMapEntry<EObject,Object>) seq.first(); if (ent == null) return Native.am_$end_of_table; return (EObject) ent.getKey(); } } return Native.am_$end_of_table; } } protected abstract void delete_object(ETuple obj); public abstract ESeq slot(); public abstract EObject select(EMatchSpec spec, int i); public EObject info(EObject item) { if (item == Native.am_owner) { return owner_pid(); } else if (item == Native.am_named_table) { return ERT.box(aname != null); } else if (item == Native.am_name) { return aname; } else if (item == Native.am_heir) { if (heirPID == null) return Native.am_none; else return heirPID; } else if (item == Native.am_size) { return ERT.box(size()); } else if (item == Native.am_memory) { return ERT.box(10*size()); } else if (item == Native.am_node) { return ERT.getLocalNode().node(); } else if (item == Native.am_type) { return type; } else if (item == Native.am_keypos) { return ERT.box( keypos1 ); } else if (item == Native.am_protection) { return access; } else if (item == Native.am_fixed) { return ERT.box(is_fixed); } else if (item == Native.am_stats) { // // The OTP test suite checks these, so we simply // provide some values that will make it happy. // This is very implementation dependent. // // {Buckets,AvgLen,StdDev,ExpSD,_MinLen,_MaxLen} // return ETuple.make( ERT.box(256), // Buckets ERT.box(7), // AvgLen, ERT.box(1), // StdDev ERT.box(1), // ExpSD ERT.box(1), // MinLen ERT.box(10) // MaxLen ); } else if (item == Native.am_safe_fixed) { throw new NotImplemented(); } else { return null; } } public void setopt(EObject head) { ETuple3 tup = ETuple3.cast(head); if (tup != null) { EInternalPID pid; if (tup.elem1 == Native.am_heir && (pid=tup.elem2.testInternalPID()) != null) { if (!pid.is_alive_dirtyread()) { this.heirPID = null; this.heirData = ERT.NIL; return; } EInternalPID old = this.heirPID; this.heirData = tup.elem3; this.heirPID = pid; if (old == null || old.remove_exit_hook(this)) { pid.add_exit_hook(this); } return; } } ETuple2 tup2 = ETuple2.cast(head); if (tup2 != null) { if (tup2.elem1 == Native.am_protection) { EObject mode = tup2.elem2; if (mode == Native.am_private || mode == Native.am_public || mode == Native.am_protected) { this.access = (EAtom) mode; return; } } else if (tup2.elem1 == Native.am_heir && tup2.elem2 == Native.am_none) { EInternalPID old = this.heirPID; this.heirPID = null; this.heirData = ERT.NIL; if (old != null) { old.remove_exit_hook(this); } return; } } throw ERT.badarg(tid, head); } protected abstract EAtom member(EObject key); protected abstract EObject last(); }