Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* added more specs for the session store
* added java SoftHashMap to avoid memory leaks of the session cache (when used with jruby and java >=1.5) * some bug fixes with the cache * some more docu
- Loading branch information
Showing
8 changed files
with
283 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -1,5 +1,5 @@ | |||
module Rack | module Rack | ||
module DataMapper | module DataMapper | ||
VERSION = '0.0.0'.freeze | VERSION = '0.2.0'.freeze | ||
end | end | ||
end | end |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,107 @@ | |||
$LOAD_PATH << File.dirname(__FILE__) | |||
require 'spec_helper' | |||
|
|||
describe DataMapper::Session::Abstract::Store do | |||
|
|||
describe 'without cache' do | |||
|
|||
def mock_session(stubs={}) | |||
@mock_session ||= mock(DataMapper::Session::Abstract::Session, stubs) | |||
end | |||
|
|||
before :each do | |||
@store = DataMapper::Session::Abstract::Store.new(nil, | |||
{}, | |||
Proc.new do | |||
1 | |||
end | |||
) | |||
end | |||
|
|||
it 'should get the session data' do | |||
DataMapper::Session::Abstract::Session.stub!(:get).and_return(mock_session) | |||
mock_session.should_receive(:data).and_return({:id => "id"}) | |||
@store.get_session({}, "sid").should == ["sid",{:id => "id"}] | |||
end | |||
|
|||
it 'should create a new session' do | |||
DataMapper::Session::Abstract::Session.should_receive(:create).and_return(mock_session) | |||
mock_session.should_receive(:data).and_return({}) | |||
result = @store.get_session({}, nil) | |||
result[0].should_not be_nil | |||
result[1].should == {} | |||
end | |||
|
|||
it 'should set the session data' do | |||
DataMapper::Session::Abstract::Session.should_receive(:create).and_return(mock_session) | |||
DataMapper::Session::Abstract::Session.should_receive(:get).twice.and_return(mock_session) | |||
mock_session.should_receive(:data).and_return({}) | |||
mock_session.should_receive(:data=).with({:id => 432}) | |||
mock_session.should_receive(:save).and_return(true) | |||
mock_session.should_receive(:data).and_return({:id => 123}) | |||
|
|||
session_id = @store.get_session({}, nil)[0] | |||
mock_session.should_receive(:session_id).and_return(session_id); | |||
@store.set_session({}, session_id, {:id => 432}, {}).should == session_id | |||
result = @store.get_session({}, session_id) | |||
|
|||
result[0].should_not be_nil | |||
result[1].should == {:id => 123} | |||
end | |||
end | |||
|
|||
describe 'with cache' do | |||
|
|||
def mock_session(stubs={}) | |||
@mock_session ||= mock(DataMapper::Session::Abstract::Session, stubs) | |||
end | |||
|
|||
before :each do | |||
@store = DataMapper::Session::Abstract::Store.new(nil, | |||
{:cache => true}, | |||
Proc.new do | |||
1 | |||
end) | |||
end | |||
|
|||
it 'should create a new session' do | |||
DataMapper::Session::Abstract::Session.should_receive(:create).and_return(mock_session) | |||
mock_session.should_receive(:data).and_return({}) | |||
result = @store.get_session({}, nil) | |||
result[0].should_not be_nil | |||
result[1].should == {} | |||
end | |||
|
|||
it 'should get the session data from storage' do | |||
DataMapper::Session::Abstract::Session.stub!(:get).and_return(mock_session) | |||
mock_session.should_receive(:data).and_return({:id => "id"}) | |||
@store.get_session({}, "sid").should == ["sid",{:id => "id"}] | |||
end | |||
|
|||
it 'should get the session data from cache' do | |||
DataMapper::Session::Abstract::Session.should_receive(:create).and_return(mock_session) | |||
mock_session.should_receive(:data).twice.and_return({}) | |||
session_id = @store.get_session({}, nil)[0] | |||
|
|||
result = @store.get_session({}, session_id) | |||
result[0].should_not be_nil | |||
result[1].should == {} | |||
end | |||
|
|||
it 'should set the session data' do | |||
DataMapper::Session::Abstract::Session.should_receive(:create).and_return(mock_session) | |||
mock_session.should_receive(:data).and_return({}) | |||
mock_session.should_receive(:data=).with({:id => 432}) | |||
mock_session.should_receive(:save).and_return(true) | |||
mock_session.should_receive(:data).and_return({:id => 123}) | |||
|
|||
session_id = @store.get_session({}, nil)[0] | |||
mock_session.should_receive(:session_id).and_return(session_id); | |||
@store.set_session({}, session_id, {:id => 432},{}).should == session_id | |||
result = @store.get_session({}, session_id) | |||
|
|||
result[0].should_not be_nil | |||
result[1].should == {:id => 123} | |||
end | |||
end | |||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,101 @@ | |||
/** | |||
* this class is taken with friendly permission to use it | |||
* from <a href="http://javaspecialists.co.za/archive/Issue098.html">javaspecialists.co.za/archive/Issue098.html</a> (section 'New SoftHashMap') | |||
*/ | |||
import java.lang.ref.*; | |||
import java.util.*; | |||
import java.io.Serializable; | |||
|
|||
public class SoftHashMap <K, V> extends AbstractMap<K, V> | |||
implements Serializable { | |||
/** The internal HashMap that will hold the SoftReference. */ | |||
private final Map<K, SoftReference<V>> hash = | |||
new HashMap<K, SoftReference<V>>(); | |||
|
|||
private final Map<SoftReference<V>, K> reverseLookup = | |||
new HashMap<SoftReference<V>, K>(); | |||
|
|||
/** Reference queue for cleared SoftReference objects. */ | |||
private final ReferenceQueue<V> queue = new ReferenceQueue<V>(); | |||
|
|||
public V get(Object key) { | |||
expungeStaleEntries(); | |||
V result = null; | |||
// We get the SoftReference represented by that key | |||
SoftReference<V> soft_ref = hash.get(key); | |||
if (soft_ref != null) { | |||
// From the SoftReference we get the value, which can be | |||
// null if it has been garbage collected | |||
result = soft_ref.get(); | |||
if (result == null) { | |||
// If the value has been garbage collected, remove the | |||
// entry from the HashMap. | |||
hash.remove(key); | |||
reverseLookup.remove(soft_ref); | |||
} | |||
} | |||
return result; | |||
} | |||
|
|||
private void expungeStaleEntries() { | |||
Reference<? extends V> sv; | |||
while ((sv = queue.poll()) != null) { | |||
hash.remove(reverseLookup.remove(sv)); | |||
} | |||
} | |||
|
|||
public V put(K key, V value) { | |||
expungeStaleEntries(); | |||
SoftReference<V> soft_ref = new SoftReference<V>(value, queue); | |||
reverseLookup.put(soft_ref, key); | |||
SoftReference<V> result = hash.put(key, soft_ref); | |||
if (result == null) return null; | |||
reverseLookup.remove(result); | |||
return result.get(); | |||
} | |||
|
|||
public V remove(Object key) { | |||
expungeStaleEntries(); | |||
SoftReference<V> result = hash.remove(key); | |||
if (result == null) return null; | |||
return result.get(); | |||
} | |||
|
|||
public void clear() { | |||
hash.clear(); | |||
reverseLookup.clear(); | |||
} | |||
|
|||
public int size() { | |||
expungeStaleEntries(); | |||
return hash.size(); | |||
} | |||
|
|||
/** | |||
* Returns a copy of the key/values in the map at the point of | |||
* calling. However, setValue still sets the value in the | |||
* actual SoftHashMap. | |||
*/ | |||
public Set<Entry<K,V>> entrySet() { | |||
expungeStaleEntries(); | |||
Set<Entry<K,V>> result = new LinkedHashSet<Entry<K, V>>(); | |||
for (final Entry<K, SoftReference<V>> entry : hash.entrySet()) { | |||
final V value = entry.getValue().get(); | |||
if (value != null) { | |||
result.add(new Entry<K, V>() { | |||
public K getKey() { | |||
return entry.getKey(); | |||
} | |||
public V getValue() { | |||
return value; | |||
} | |||
public V setValue(V v) { | |||
entry.setValue(new SoftReference<V>(v, queue)); | |||
return value; | |||
} | |||
}); | |||
} | |||
} | |||
return result; | |||
} | |||
} |