44 [clojure.string :as str])
55 (:import
66 [java.io BufferedReader Writer]
7- [java.net ServerSocket])
7+ [java.net ServerSocket]
8+ [java.util.concurrent Executors ScheduledExecutorService TimeUnit])
89 (:gen-class ))
910
1011
1314 (atom {}))
1415
1516
17+ (def keys-to-expire
18+ (atom []))
19+
20+
21+ (defn expire-key [key]
22+ (fn []
23+ (swap! database assoc key nil )
24+ (swap! keys-to-expire
25+ (fn [expire-keys]
26+ (remove #(= (second %) key) expire-keys)))))
27+
28+
29+ (defn set-key-to-expire [key timestamp]
30+ (swap! keys-to-expire
31+ (fn [expire-keys]
32+ (->> (conj expire-keys [timestamp key])
33+ (sort-by first)
34+ (vec )))))
35+
36+
37+ (comment
38+ @keys-to-expire
39+ (set-key-to-expire " name" 123123123 )
40+ (expire-key " name" ))
41+
42+
43+ (def ^ScheduledExecutorService cleanup-pool
44+ (Executors/newScheduledThreadPool 10 ))
45+
46+
47+ (defn schedule-cleanup-task []
48+ (let [current-time (System/currentTimeMillis )
49+ five-seconds-later (+ current-time 5000 )
50+ keys-to-schedule (->> @keys-to-expire
51+ (take-while #(< (first %) five-seconds-later)))]
52+ (doseq [[timestamp key] keys-to-schedule]
53+ (.schedule cleanup-pool
54+ ^Runnable (expire-key key)
55+ ^Long (- timestamp current-time)
56+ TimeUnit/MILLISECONDS))))
57+
58+
59+ (defn start-cleanup-worker []
60+ (.scheduleAtFixedRate cleanup-pool schedule-cleanup-task 0 5 TimeUnit/SECONDS))
61+
62+
1663
1764; ; обрабатываем команды от клиентов
1865(defmulti handle-command
3683 (swap! database assoc key val)
3784
3885 (when (and opt (= (.toUpperCase opt) " PX" ))
39- (let [timeout (Integer/parseInt optarg)]
40- (future
41- (Thread/sleep timeout)
42- (swap! database dissoc key))))
86+ (let [delay (Integer/parseInt optarg)
87+ current-time (System/currentTimeMillis )
88+ timestamp (+ current-time delay)]
89+ (if (< timestamp (+ current-time 5000 ))
90+ (.schedule cleanup-pool ^Runnable (expire-key key) delay TimeUnit/MILLISECONDS)
91+ (set-key-to-expire key timestamp))))
92+
4393 ; ; ответ клиенту
4494 " OK" )
4595
4696
4797(defmethod handle-command :get
4898 [[_ [key-len key]]]
49- (if-some [entry (find @database key)]
50- (val entry)
51- " (nil)" ))
99+ (let [entry (find @database key)]
100+ (if (some? (val entry))
101+ (val entry)
102+ " (nil)" )))
52103
53104
54105; ; needed for redis-cli
129180 (doseq [msg-in (repeatedly #(read-message reader))
130181 :while (not (empty? msg-in))
131182 :let [msg-out (handler msg-in)]]
132- (println " msg-in" msg-in " msg-out " msg-out " \n " )
183+ (println " msg-in" msg-in)
133184 ; ; отправляем ответ
134185 (send-message writer msg-out)))))
135186
154205
155206
156207; ; graceful shutdown
157- (defn shutdown-hook [server]
208+ (defn shutdown-hook [server worker ]
158209 (.addShutdownHook (Runtime/getRuntime )
159210 (Thread. ^Runnable
160211 (fn []
161212 (.close server)
213+ (future-cancel worker)
214+ (.shutdown cleanup-pool)
162215 (shutdown-agents )))))
163216
164217
165218; ; точка входа
166219(defn -main
167220 [& args]
168- (let [server (run-server 6379 handle-message)]
169- (shutdown-hook server)
221+ (let [server (run-server 6379 handle-message)
222+ worker (start-cleanup-worker )]
223+ (shutdown-hook server worker)
170224 server))
171225
172226
176230 (-main ))
177231
178232 @database
233+ @keys-to-expire
179234
180235 (.close server)
181236 nil )
0 commit comments