lib/ldap/server/connection.rb in ruby-ldapserver-0.3.1 vs lib/ldap/server/connection.rb in ruby-ldapserver-0.4.0
- old
+ new
@@ -17,26 +17,34 @@
def initialize(io, opt={})
@io = io
@opt = opt
@mutex = Mutex.new
- @active_reqs = {} # map message ID to thread object
+ @threadgroup = ThreadGroup.new
@binddn = nil
@version = 3
- @logger = @opt[:logger] || $stderr
+ @logger = @opt[:logger]
@ssl = false
startssl if @opt[:ssl_on_connect]
end
- def log(msg)
- @logger << "[#{@io.peeraddr[3]}]: #{msg}\n"
+ def log(msg, severity = Logger::INFO)
+ @logger.add(severity, msg, @io.peeraddr[3])
end
+
+ def debug msg
+ log msg, Logger::DEBUG
+ end
+
+ def log_exception(e)
+ log "#{e}: #{e.backtrace.join("\n\tfrom ")}", Logger::ERROR
+ end
def startssl # :yields:
@mutex.synchronize do
- raise LDAP::ResultError::OperationsError if @ssl or @active_reqs.size > 0
+ raise LDAP::ResultError::OperationsError if @ssl or @threadgroup.list.size > 0
yield if block_given?
@io = OpenSSL::SSL::SSLSocket.new(@io, @opt[:ssl_ctx])
@io.sync_close = true
@io.accept
@ssl = true
@@ -47,13 +55,16 @@
# Return String containing the raw element.
def ber_read(io)
blk = io.read(2) # minimum: short tag, short length
throw(:close) if blk.nil?
- tag = blk[0] & 0x1f
- len = blk[1]
+ codepoints = blk.respond_to?(:codepoints) ? blk.codepoints.to_a : blk
+
+ tag = codepoints[0] & 0x1f
+ len = codepoints[1]
+
if tag == 0x1f # long form
tag = 0
while true
ch = io.getc
blk << ch
@@ -114,82 +125,26 @@
when 2 # UnbindRequest
throw(:close)
when 3 # SearchRequest
- # Note: RFC 2251 4.4.4.1 says behaviour is undefined if
- # client sends an overlapping request with same message ID,
- # so we don't have to worry about the case where there is
- # already a thread with this id in @active_reqs.
- # However, to avoid a race we copy messageId/
- # protocolOp/controls into thread-local variables, because
- # they will change when the next request comes in.
- #
- # There is a theoretical race condition here: a client could
- # send an abandon request before Thread.current is assigned to
- # @active_reqs[thrm]. It's not a problem, because abandon isn't
- # guaranteed to work anyway. Doing it this way ensures that
- # @active_reqs does not leak memory on a long-lived connection.
+ start_op(messageId,protocolOp,controls,:do_search)
- Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
- begin
- @active_reqs[thrm] = Thread.current
- operationClass.new(self,thrm,*ocArgs).do_search(thrp, thrc)
- ensure
- @active_reqs.delete(thrm)
- end
- end
-
when 6 # ModifyRequest
- Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
- begin
- @active_reqs[thrm] = Thread.current
- operationClass.new(self,thrm,*ocArgs).do_modify(thrp, thrc)
- ensure
- @active_reqs.delete(thrm)
- end
- end
+ start_op(messageId,protocolOp,controls,:do_modify)
when 8 # AddRequest
- Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
- begin
- @active_reqs[thrm] = Thread.current
- operationClass.new(self,thrm,*ocArgs).do_add(thrp, thrc)
- ensure
- @active_reqs.delete(thrm)
- end
- end
+ start_op(messageId,protocolOp,controls,:do_add)
when 10 # DelRequest
- Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
- begin
- @active_reqs[thrm] = Thread.current
- operationClass.new(self,thrm,*ocArgs).do_del(thrp, thrc)
- ensure
- @active_reqs.delete(thrm)
- end
- end
+ start_op(messageId,protocolOp,controls,:do_del)
when 12 # ModifyDNRequest
- Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
- begin
- @active_reqs[thrm] = Thread.current
- operationClass.new(self,thrm,*ocArgs).do_modifydn(thrp, thrc)
- ensure
- @active_reqs.delete(thrm)
- end
- end
+ start_op(messageId,protocolOp,controls,:do_modifydn)
when 14 # CompareRequest
- Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
- begin
- @active_reqs[thrm] = Thread.current
- operationClass.new(self,thrm,*ocArgs).do_compare(thrp, thrc)
- ensure
- @active_reqs.delete(thrm)
- end
- end
+ start_op(messageId,protocolOp,controls,:do_compare)
when 16 # AbandonRequest
abandon(protocolOp.value)
else
@@ -205,10 +160,35 @@
end
end
abandon_all
end
+ # Start an operation in a Thread. Add this to a ThreadGroup to allow
+ # the operation to be abandoned later.
+ #
+ # When the thread terminates, it automatically drops out of the group.
+ #
+ # Note: RFC 2251 4.4.4.1 says behaviour is undefined if
+ # client sends an overlapping request with same message ID,
+ # so we don't have to worry about the case where there is
+ # already a thread with this messageId in @threadgroup.
+
+ def start_op(messageId,protocolOp,controls,meth)
+ operationClass = @opt[:operation_class]
+ ocArgs = @opt[:operation_args] || []
+ thr = Thread.new do
+ begin
+ operationClass.new(self,messageId,*ocArgs).
+ send(meth,protocolOp,controls)
+ rescue Exception => e
+ log_exception e
+ end
+ end
+ thr[:messageId] = messageId
+ @threadgroup.add(thr)
+ end
+
def write(data)
@mutex.synchronize do
@io.write(data)
@io.flush
end
@@ -221,21 +201,19 @@
end
end
def abandon(messageID)
@mutex.synchronize do
- thread = @active_reqs.delete(messageID)
- thread.raise LDAP::Abandon if thread and thread.alive?
+ thread = @threadgroup.list.find { |t| t[:messageId] == messageID }
+ thread.raise LDAP::Abandon if thread
end
end
def abandon_all
- return if @active_reqs.size == 0
@mutex.synchronize do
- @active_reqs.each do |id, thread|
- thread.raise LDAP::Abandon if thread.alive?
+ @threadgroup.list.each do |thread|
+ thread.raise LDAP::Abandon
end
- @active_reqs = {}
end
end
def send_unsolicited_notification(resultCode, opt={})
protocolOp = [