Skip to content

Commit

Permalink
GC-based resource management (#1)
Browse files Browse the repository at this point in the history
* GC-based cleanup of resources are now possible.

* 3.0

* snap
  • Loading branch information
cnuernber committed Nov 17, 2018
1 parent 6e9f506 commit 0b3ad55
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 2 deletions.
25 changes: 25 additions & 0 deletions java/tech/resource/GCReference.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package tech.resource;

import java.lang.ref.*;
import java.util.concurrent.Callable;



public class GCReference extends WeakReference<Object>
{
Callable disposer;
public GCReference( Object item, ReferenceQueue<Object> q, Callable _disposer)
{
super(item, q);
disposer = _disposer;
}
public void dispose() throws Exception
{
synchronized(this) {
if(disposer != null) {
disposer.call();
disposer = null;
}
}
}
}
5 changes: 3 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(defproject techascent/tech.resource "2.1-SNAPSHOT"
(defproject techascent/tech.resource "3.1-SNAPSHOT"
:description "Exception-safe threadsafe resource management"
:url "http://github.com/tech-ascent/tech.resource"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.9.0"]])
:dependencies [[org.clojure/clojure "1.9.0"]]
:java-source-paths ["java"])
68 changes: 68 additions & 0 deletions src/tech/gc_resource.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
(ns tech.gc-resource
(:require [tech.resource :as resource])
(:import [java.lang.ref ReferenceQueue]
[java.lang Thread]
[tech.resource GCReference]))

(set! *warn-on-reflection* true)


(def ^:dynamic *reference-queue* (ReferenceQueue.))


(defn watch-reference-queue
[run-atom ^ReferenceQueue reference-queue]
(try
(println :tech.gc-resource "Reference thread starting")
(loop [continue? @run-atom]
(when continue?
(let [next-ref (.remove reference-queue 100)]
(when next-ref
(if-not (satisfies? resource/PResource next-ref)
(println :tech.gc-resource "ReferenceItem in queue is not releasable!!")
(try
(resource/release-resource next-ref)
(catch Throwable e
(println :tech.gc-resource "Failed to release resource:" next-ref e)))))
(recur @run-atom))))
(catch Throwable e
(println :tech.gc-resource "!!Error in reference queue!!:" e)))
(println :tech.gc-resource "Reference queue exiting"))


(defn start-reference-thread
[]
(let [run-atom (atom true)
thread (Thread. #(watch-reference-queue run-atom *reference-queue*))]
(.start thread)
{:thread thread
:close-fn #(do
(reset! run-atom false)
(.join thread))}))


(def ^:dynamic *reference-thread* (start-reference-thread))


(extend-protocol resource/PResource
GCReference
(release-resource [^GCReference item]
(.dispose item)))



(defn track-gc-only
"Track this item using weak references. Note that the dispose-fn must absolutely
*not* reference the item else nothing will ever get released."
[item dispose-fn]
(GCReference. item ^ReferenceQueue *reference-queue* dispose-fn)
item)


(defn track
"Track an item via both the gc system *and* the stack based system.
Dispose will be first-one-wins."
[item dispose-fn]
(let [gc-ref (GCReference. item *reference-queue* dispose-fn)]
(resource/track gc-ref)
item))
38 changes: 38 additions & 0 deletions test/tech/gc_resource_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
(ns tech.gc_resource_test
(:require [tech.gc-resource :as gc-resource]
[tech.resource :as resource]
[clojure.test :refer :all]))


(deftest gc-resources
(testing "System.gc cleans up the things"
(let [counter (atom 0)]
(let [create-fn (fn []
(swap! counter inc)
(gc-resource/track (Object.) #(do
(swap! counter dec))))]
(->> (repeatedly 100 #(create-fn))
dorun)
(is (= 100 @counter))
(System/gc)
(Thread/sleep 100)
(is (= 0 @counter)))
(System/gc)
(is (= 0 @counter))))
(testing "resource context and system.gc work together"
(let [counter (atom 0)]
(resource/with-resource-context
(let [create-fn (fn []
(swap! counter inc)
(gc-resource/track (Object.) #(swap! counter dec)))
objects (vec (repeatedly 100 #(create-fn)))]
(is (= 100 @counter))
(System/gc)
(Thread/sleep 100)
(is (= 100 @counter))
;;The compiler is careful to null out things that are no longer in use.
objects))
(is (= 0 @counter))
(System/gc)
(Thread/sleep 100)
(is (= 0 @counter)))))

0 comments on commit 0b3ad55

Please sign in to comment.