Skip to content

Commit f5ee4c0

Browse files
committed
More sophisticated socket handling.
Now we can handle non-blocking socket operations.
1 parent db16f71 commit f5ee4c0

File tree

5 files changed

+101
-6
lines changed

5 files changed

+101
-6
lines changed
152 Bytes
Binary file not shown.
97 Bytes
Binary file not shown.

src/clj_libssh2/libssh2/keepalive.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
(def config (jna/to-fn Void ssh2/libssh2_keepalive_config))
88

99
; int libssh2_keepalive_send (LIBSSH2_SESSION *session, int *seconds_to_next);
10-
(def send (jna/to-fn Void ssh2/libssh2_keepalive_send))
10+
(def send (jna/to-fn Integer ssh2/libssh2_keepalive_send))

src/clj_libssh2/session.clj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
(when (> 0 (:socket session))
9191
(destroy-session session "Shutting down due to bad socket." true))
9292
(try
93+
(libssh2-session/set-blocking (:session session) 0)
9394
(handshake session)
9495
(known-hosts/check session)
9596
(authenticate session credentials)
@@ -98,3 +99,7 @@
9899
(catch Exception e
99100
(close session)
100101
(throw e))))))))
102+
103+
(defn get-timeout
104+
[session]
105+
(libssh2-session/get-timeout (:session session)))

src/clj_libssh2/socket.clj

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
(ns clj-libssh2.socket
2-
(:require [net.n01se.clojure-jna :as jna]))
2+
(:require [net.n01se.clojure-jna :as jna]
3+
[clj-libssh2.error :refer [handle-errors]]
4+
[clj-libssh2.libssh2 :as libssh2]
5+
[clj-libssh2.libssh2.keepalive :as libssh2-keepalive]
6+
[clj-libssh2.libssh2.session :as libssh2-session])
7+
(:import [com.sun.jna.ptr IntByReference]))
8+
9+
;; Constants from libsimplesocket.h
10+
11+
(def SIMPLE_SOCKET_BAD_ADDRESS -1)
12+
(def SIMPLE_SOCKET_SOCKET_FAILED -2)
13+
(def SIMPLE_SOCKET_CONNECT_FAILED -3)
14+
15+
(def SIMPLE_SOCKET_SELECT_READ 1)
16+
(def SIMPLE_SOCKET_SELECT_WRITE 2)
17+
(def SIMPLE_SOCKET_SELECT_ERROR 4)
318

419
(def close
520
"Close a socket"
@@ -14,9 +29,84 @@
1429
port)]
1530
(when (> 0 socket)
1631
;; Magic numbers are from libsimplesocket.h
17-
(throw (Exception. (case socket
18-
-1 (format "%s is not a valid IP address" address)
19-
-2 "Failed to create a TCP socket"
20-
-3 (format "Failed to connect to %s:%d" address port)
32+
(throw (Exception. (condp = socket
33+
SIMPLE_SOCKET_BAD_ADDRESS
34+
(format "%s is not a valid IP address" address)
35+
36+
SIMPLE_SOCKET_SOCKET_FAILED
37+
"Failed to create a TCP socket"
38+
39+
SIMPLE_SOCKET_CONNECT_FAILED
40+
(format "Failed to connect to %s:%d" address port)
41+
2142
"An unknown error occurred"))))
2243
socket))
44+
45+
(defn select
46+
"Call select on a socket from a clj-libssh2 Session."
47+
[session select-read select-write timeout]
48+
(jna/invoke Integer
49+
simplesocket/simple_socket_select
50+
(:socket session)
51+
(bit-or
52+
(if select-read SIMPLE_SOCKET_SELECT_READ 0)
53+
(if select-write SIMPLE_SOCKET_SELECT_WRITE 0))
54+
timeout))
55+
56+
(defn send-keepalive
57+
"Send a keepalive message and return the number of seconds until the next
58+
time we should send a keepalive."
59+
[session]
60+
(let [seconds-to-wait (IntByReference.)]
61+
(handle-errors session
62+
(libssh2-keepalive/send (:session session) seconds-to-wait))
63+
(.getValue seconds-to-wait)))
64+
65+
(defn wait
66+
"Roughly equivalent to _libssh2_wait_socket in libssh2. Will raise an error
67+
on timeout or just block until it's time to try again."
68+
([session]
69+
(wait session (System/currentTimeMillis)))
70+
([session start-time]
71+
(let [ms-until-next-keepalive (* (send-keepalive session) 1000)
72+
block-directions (libssh2-session/block-directions (:session session))
73+
block-for-read (boolean (bit-and block-directions libssh2/SESSION_BLOCK_INBOUND))
74+
block-for-write (boolean (bit-and block-directions libssh2/SESSION_BLOCK_OUTBOUND))
75+
libssh2-timeout-ms (libssh2-session/get-timeout (:session session))
76+
select-until (partial select session block-for-read block-for-write)
77+
select-result (cond (and (< 0 libssh2-timeout-ms)
78+
(or (= 0 ms-until-next-keepalive)
79+
(> ms-until-next-keepalive libssh2-timeout-ms)))
80+
(let [elapsed (- (System/currentTimeMillis) start-time)]
81+
(when (> elapsed libssh2-timeout-ms)
82+
(handle-errors session libssh2/ERROR_TIMEOUT))
83+
(select-until (- libssh2-timeout-ms elapsed)))
84+
85+
(< 0 ms-until-next-keepalive)
86+
(select-until ms-until-next-keepalive)
87+
88+
:else
89+
(select-until 0))]
90+
(when (>= 0 select-result)
91+
(handle-errors session libssh2/ERROR_TIMEOUT)))))
92+
93+
(defmacro block
94+
"Turn a non-blocking call that returns EAGAIN into a blocking one."
95+
[session & body]
96+
`(let [start-time# (-> (System/currentTimeMillis) (/ 1000.0) Math/round)]
97+
(while (= libssh2/ERROR_EAGAIN (do ~@body))
98+
(handle-errors ~session
99+
(wait ~session start-time#)))))
100+
101+
(defmacro block-return
102+
"Similar to block, but for functions that return a pointer"
103+
[session & body]
104+
`(let [start-time# (-> (System/currentTimeMillis) (/ 1000.0) Math/round)]
105+
(loop [result# (do ~@body)]
106+
(if (nil? result#)
107+
(let [errno# (libssh2-session/last-errno (:session ~session))]
108+
(handle-errors ~session errno#)
109+
(when (= libssh2/ERROR_EAGAIN errno#)
110+
(wait ~session start-time#))
111+
(recur (do ~@body)))
112+
result#))))

0 commit comments

Comments
 (0)