Skip to content

Commit

Permalink
Merge branch 'consolidate-timeouts'
Browse files Browse the repository at this point in the history
  • Loading branch information
conormcd committed Apr 9, 2016
2 parents 5df37e1 + 77829fe commit e5cb1d8
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 101 deletions.
10 changes: 5 additions & 5 deletions src/clj_libssh2/agent.clj
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
[session ssh-agent previous]
(when (nil? previous)
(handle-errors session
(with-timeout :agent
(with-timeout session :agent
(libssh2-agent/list-identities ssh-agent))))
(let [id (PointerByReference.)
ret (handle-errors session
(with-timeout :agent
(with-timeout session :agent
(libssh2-agent/get-identity ssh-agent id previous)))]
(case ret
0 (.getValue id)
Expand Down Expand Up @@ -59,12 +59,12 @@
(try
(log/info "Connecting to the SSH agent...")
(handle-errors session
(with-timeout :agent
(with-timeout session :agent
(libssh2-agent/connect ssh-agent)))
(when (loop [previous nil]
(log/info "Fetching an ID to authenticate with...")
(if-let [id (get-identity session ssh-agent previous)]
(when-not (= 0 (with-timeout :agent
(when-not (= 0 (with-timeout session :agent
(libssh2-agent/userauth ssh-agent username id)))
(recur id))
:fail))
Expand All @@ -75,7 +75,7 @@
true
(finally
(handle-errors session
(with-timeout :agent
(with-timeout session :agent
(log/info "Disconnecting from the agent...")
(libssh2-agent/disconnect ssh-agent)))
(libssh2-agent/free ssh-agent)))))
4 changes: 2 additions & 2 deletions src/clj_libssh2/authentication.clj
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
(when-not (.exists (file keyfile))
(error/raise (FileNotFoundException. keyfile))))
(handle-errors session
(with-timeout :auth
(with-timeout session :auth
(libssh2-userauth/publickey-fromfile (:session session)
(:username credentials)
(:public-key credentials)
Expand All @@ -70,7 +70,7 @@
[session credentials]
(log/info "Authenticating using a username and password.")
(handle-errors session
(with-timeout :auth
(with-timeout session :auth
(libssh2-userauth/password (:session session)
(:username credentials)
(:password credentials))))
Expand Down
20 changes: 10 additions & 10 deletions src/clj_libssh2/channel.clj
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
0 on success. An exception will be thrown if an error occurs."
[session channel]
(log/info "Closing channel.")
(block session
(handle-errors session (libssh2-channel/close channel))))
(block session :lib (handle-errors session (libssh2-channel/close channel))))

(defn exec
"Execute a command on the remote host. This merely starts the execution of
Expand All @@ -44,7 +43,7 @@
0 on success. An exception will be thrown if an error occurs."
[session channel commandline]
(log/info "Executing a command on the remote host.")
(block session
(block session :request
(handle-errors session (libssh2-channel/exec channel commandline))))

(defn exit-signal
Expand Down Expand Up @@ -117,7 +116,7 @@
0 on success or throws an exception on failure."
[session channel]
(block session (handle-errors session (libssh2-channel/free channel))))
(block session :lib (handle-errors session (libssh2-channel/free channel))))

(defn open
"Create a new channel for a session.
Expand All @@ -131,7 +130,7 @@
A newly-allocated channel object, or throws exception on failure."
[session]
(log/info "Opening a new channel.")
(block-return session (libssh2-channel/open-session (:session session))))
(block-return session :lib (libssh2-channel/open-session (:session session))))

(defn open-scp-recv
"Create a new channel dedicated to receiving a file using SCP.
Expand All @@ -149,7 +148,7 @@
[session remote-path]
(log/info "Opening a new channel to receive a file using SCP.")
(let [fileinfo (Stat/newInstance)]
{:channel (block-return session
{:channel (block-return session :request
(libssh2-scp/recv2 (:session session) remote-path fileinfo))
:fileinfo fileinfo}))

Expand All @@ -175,7 +174,7 @@
(let [mode (or mode 0644)
mtime (or mtime 0)
atime (or atime 0)]
(block-return session
(block-return session :request
(libssh2-scp/send64 (:session session) remote-path mode size mtime atime))))

(defn send-eof
Expand All @@ -191,7 +190,8 @@
0 on success, throws an exception on failure."
[session channel]
(log/info "Notifying the remote host of EOF")
(block session (handle-errors session (libssh2-channel/send-eof channel))))
(block session :request
(handle-errors session (libssh2-channel/send-eof channel))))

(defn setenv
"Set a selection of environment variables on the channel. These will be
Expand Down Expand Up @@ -224,7 +224,7 @@
{:session session})
ret))]
(doseq [[k v] env]
(block session
(block session :request
(handle-errors session
(fail-if-forbidden
(libssh2-channel/setenv channel (->str k) (->str v))))))))
Expand Down Expand Up @@ -419,7 +419,7 @@
nil, or throw an exception if the timeout is exceeded on any of the streams
given."
[session channel streams]
(let [read-timeout (-> session :options :read-timeout)
(let [read-timeout (-> session :options :timeout :read)
last-read-time (->> streams
(remove #(= :input (:direction %)))
(map :last-read-time)
Expand Down
71 changes: 26 additions & 45 deletions src/clj_libssh2/error.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@
[clj-libssh2.libssh2.session :as libssh2-session])
(:import [com.sun.jna.ptr IntByReference PointerByReference]))

(def timeouts
"An atom map where the keys are keywords being the symbolic names of named
timeouts (see get-timeout/set-timeout) and the values are the number of
milliseconds for that timeout."
(atom {:agent 10000
:auth 10000
:known-hosts 10000}))

(def error-messages
"All of the error codes that are documented for libssh2 except for
LIBSSH2_ERROR_SOCKET_NONE which despite its name is a generic error and
Expand Down Expand Up @@ -172,33 +164,20 @@
(maybe-throw-error (:session session#) res#)
res#))

(defn get-timeout
"Get a timeout value.
(defn enforce-timeout
"Throw an error if a timeout has been exceeded.
Arguments:
name-or-value Either the name of a symbolic timeout (e.g. :session) or a
number of milliseconds.
Return:
A number of milliseconds for the timeout."
[name-or-value]
(or (get @timeouts name-or-value) name-or-value 1000))

(defn set-timeout
"Update a timeout value.
Arguments:
timeout-name The name of a symbolic timeout.
millis The number of milliseconds for that timeout.
Return:
A map of all the current symbolic timeouts."
[timeout-name millis]
(swap! timeouts assoc timeout-name millis))
session The clj-libssh2.session.Session object for the current session.
start-time The time the timeout is relative to.
timeout The number of milliseconds describing the timeout value."
[session start-time timeout]
(when (< (if (keyword? timeout)
(-> session :options :timeout timeout)
timeout)
(- (System/currentTimeMillis) start-time))
(handle-errors session libssh2/ERROR_TIMEOUT)))

(defmacro with-timeout
"Run some code that could return libssh2/ERROR_EAGAIN and if it does, retry
Expand All @@ -213,22 +192,24 @@
Arguments:
timeout Either a number of milliseconds or a keyword referring to a named
timeout set using set-timeout.
session The clj-libssh2.session.Session object for the current session.
timeout A number of milliseconds specifying the timeout. This macro will
wait for at least that number of milliseconds before failing with a
timeout error. It may return successfully sooner, but this value is
the minimum time you will wait for failure. The argument can also be
passed a keyword which will be looked up in the :timeout section of
the session options.
Return:
Either the return value of the code being wrapped or an exception if the
timeout is exceeded."
[timeout & body]
[session timeout & body]
`(let [start# (System/currentTimeMillis)
timeout# (get-timeout ~timeout)]
(loop [timedout# false]
(if timedout#
(raise (format "Timeout of %d ms exceeded" timeout#)
{:timeout ~timeout
:timeout-length timeout#})
(let [r# (do ~@body)]
(if (= r# libssh2/ERROR_EAGAIN)
(recur (< timeout# (- (System/currentTimeMillis) start#)))
r#))))))
session# ~session
timeout# ~timeout]
(loop [result# (do ~@body)]
(enforce-timeout session# start# timeout#)
(if (= result# libssh2/ERROR_EAGAIN)
(recur (do ~@body))
result#))))
4 changes: 2 additions & 2 deletions src/clj_libssh2/known_hosts.clj
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
0 on success or an exception if the key does not validate."
[session known-hosts host port host-key fail-on-missing fail-on-mismatch]
(handle-errors session
(with-timeout :known-hosts
(with-timeout session :known-hosts
(checkp-result fail-on-mismatch fail-on-missing
(libssh2-knownhost/checkp known-hosts
host
Expand Down Expand Up @@ -112,7 +112,7 @@
[session known-hosts known-hosts-file]
(when (.exists (file known-hosts-file))
(handle-errors session
(with-timeout :known-hosts
(with-timeout session :known-hosts
(libssh2-knownhost/readfile known-hosts
known-hosts-file
libssh2/KNOWNHOST_FILE_OPENSSH)))))
Expand Down
22 changes: 14 additions & 8 deletions src/clj_libssh2/session.clj
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@
protect against attempting to close sessions twice."
(atom #{}))

(def default-opts
(def ^:private default-opts
"The default options for a session. These are not only the defaults, but an
exhaustive list of the legal options."
{:blocking-timeout 60000
:character-set "UTF-8"
{:character-set "UTF-8"
:fail-if-not-in-known-hosts false
:fail-unless-known-hosts-matches true
:known-hosts-file nil
:read-chunk-size (* 1024 1024)
:read-timeout 60000
:timeout {:agent 5000 ; All agent operations
:auth 5000 ; All authentication calls
:known-hosts 5000 ; All interactions with the known hosts API
:lib 1000 ; Operations that are 100% local library calls
:read 60000 ; Reads from the remote host
:request 5000} ; Simple calls to the remote host
:write-chunk-size (* 1024 1024)})

(defrecord Session [session socket host port options])
Expand Down Expand Up @@ -56,7 +60,9 @@
keys in default-opts."
[opts]
{:pre [(map? opts) (every? (set (keys default-opts)) (keys opts))]}
(merge default-opts opts))
(merge default-opts
(assoc opts
:timeout (merge (:timeout default-opts) (:timeout opts)))))

(defn- destroy-session
"Free a libssh2 session object from a Session and optionally raise an
Expand All @@ -77,10 +83,10 @@
(destroy-session session "Shutting down normally." false))
([session reason raise]
(log/info "Tearing down the session.")
(socket/block session
(socket/block session :request
(handle-errors session
(libssh2-session/disconnect (:session session) reason)))
(socket/block session
(socket/block session :lib
(handle-errors session
(libssh2-session/free (:session session))))
(when raise
Expand All @@ -98,7 +104,7 @@
0 on success. Throws an exception on failure."
[session]
(log/info "Handshaking with the remote host.")
(socket/block session
(socket/block session :request
(handle-errors session
(libssh2-session/handshake (:session session) (:socket session)))))

Expand Down
20 changes: 8 additions & 12 deletions src/clj_libssh2/socket.clj
Original file line number Diff line number Diff line change
Expand Up @@ -95,33 +95,29 @@
(when (>= 0 select-result)
(handle-errors session libssh2/ERROR_TIMEOUT))))))

(defn enforce-blocking-timeout
[session start-time]
(when (< (-> session :options :blocking-timeout)
(- (System/currentTimeMillis) start-time))
(handle-errors session libssh2/ERROR_TIMEOUT)))

(defmacro block
"Turn a non-blocking call that returns EAGAIN into a blocking one."
[session & body]
[session timeout & body]
`(let [session# ~session
start-time# (System/currentTimeMillis)]
start-time# (System/currentTimeMillis)
timeout# ~timeout]
(while (= libssh2/ERROR_EAGAIN (do ~@body))
(handle-errors session#
(wait session# start-time#))
(enforce-blocking-timeout session# start-time#))))
(error/enforce-timeout session# start-time# timeout#))))

(defmacro block-return
"Similar to block, but for functions that return a pointer"
[session & body]
[session timeout & body]
`(let [session# ~session
start-time# (System/currentTimeMillis)]
start-time# (System/currentTimeMillis)
timeout# ~timeout]
(loop [result# (do ~@body)]
(if (nil? result#)
(let [errno# (libssh2-session/last-errno (:session session#))]
(handle-errors session# errno#)
(when (= libssh2/ERROR_EAGAIN errno#)
(wait session# start-time#))
(enforce-blocking-timeout session# start-time#)
(error/enforce-timeout session# start-time# timeout#)
(recur (do ~@body)))
result#))))
2 changes: 1 addition & 1 deletion src/clj_libssh2/ssh.clj
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
(let [output (FileOutputStream. local-path)
file-size (.getSize fileinfo)
read-chunk-size (-> session :options :read-chunk-size)
read-timeout (-> session :options :read-timeout)
read-timeout (-> session :options :timeout :read)
finish (fn [bytes-read]
(.close output)
{:local-path local-path
Expand Down
Loading

0 comments on commit e5cb1d8

Please sign in to comment.