igrigorik/em-synchrony — Issue #73: Former connection lost when we call 'acquire' twice in the same fiber
Author: qqshfox
Created: 2011-10-25 03:24:53+00:00
State: open

From igrigorik/em-synchrony#72

```
* if a "sync" call is made, then fiber is paused until operation is done and connection is automatically returned to the pool
```
This is not always true i think.
if the 'sync' call invoke another sync call, the first conn wont be returned back to the pool till the second finished.
In this case, the connection should be reused.

As the current implementation, we will lost the former connection if we call the 'acquire' method twice:

```ruby
def acquire(fiber)
if conn = @available.pop
@reserved[fiber.object_id] = conn
conn
else
Fiber.yield @pending.push fiber
acquire(fiber)
end
end
```
and still 'release' twice:

```ruby
def execute(async)
f = Fiber.current

begin
conn = acquire(f)
yield conn
ensure
release(f) if not async
end
end

def release(fiber)
@available.push(@reserved.delete(fiber.object_id))

if pending = @pending.shift
pending.resume
end
end
```
then push a 'nil' to the list 'available'.

This is a case to reproduce the problem:

```ruby
require 'em-synchrony'

CACHE_HASH = {}

class CacheClient < Hash

# the following three methods are all sync
# get value from CACHE_HASH
def get(key)
puts "#{self} get: #{key}"
CACHE_HASH[key]
end

# get value from CACHE_HASH
# if value does not exist
# then yield to accept a value
# then put it into the CACHE_HASH
# and return the value
def fetch(key)
puts "#{self} fetch: #{key}"
if value = get(key)
value
else
value = yield
set key, value
value
end
end

# put the value into CACHE_HASH
def set(key, value)
puts "#{self} set: #{key}, value: #{value}"
CACHE_HASH[key] = value
end

def inspect
"#{self.to_s}<#{super}>"
end

def to_s
"#{self.class.name}@#{self.object_id}"
end
end

CACHE = EM::Synchrony::ConnectionPool.new :size => 2 do
CacheClient.new
end

class CacheUtil
class << self

def get(key)
CACHE.get key
end

def fetch(key)
CACHE.fetch key
end

def set(key, value)
CACHE.set key, value
end
end
end

def log(key)
puts "+" * 20
puts key
puts "available: #{CACHE.instance_variable_get(:@available).inspect}"
puts "available size: #{CACHE.instance_variable_get(:@available).size}"
puts "reserved: #{CACHE.instance_variable_get(:@reserved).inspect}"
puts "reserved size: #{CACHE.instance_variable_get(:@reserved).size}"
puts "CACHE_HASH: #{CACHE_HASH.inspect}"
puts "-" * 20
end

log "0"
# the methods are all sync
CACHE.set "key2", "value2" # put "key2" into cache
CACHE.fetch "key1" do # get "key1", not exist; acquire
log "1"
# what we put into key1 are based on the value of key2
value2 = CACHE.get "key2" # get "key2"; acquire
# release
log "2"

"just_for_test:" + value2 # generate value for "key1" based on value of "key2"
# put the value into "key1"
end # release
log "3"
```

result:

```
++++++++++++++++++++
0
available: [CacheClient@5756940<{}>, CacheClient@5756920<{}>]
available size: 2
reserved: {}
reserved size: 0
CACHE_HASH: {}
--------------------
CacheClient@5756920 set: key2, value: value2
CacheClient@5756920 fetch: key1
CacheClient@5756920 get: key1
++++++++++++++++++++
1
available: [CacheClient@5756940<{}>]
available size: 1
reserved: {5755000=>CacheClient@5756920<{}>}
reserved size: 1
CACHE_HASH: {"key2"=>"value2"}
--------------------
CacheClient@5756940 get: key2
++++++++++++++++++++
2
available: [CacheClient@5756940<{}>]
available size: 1
reserved: {}
reserved size: 0
CACHE_HASH: {"key2"=>"value2"}
--------------------
CacheClient@5756920 set: key1, value: just_for_test:value2
++++++++++++++++++++
3
available: [CacheClient@5756940<{}>, nil]
available size: 2
reserved: {}
reserved size: 0
CACHE_HASH: {"key2"=>"value2", "key1"=>"just_for_test:value2"}
--------------------
```