Skip to content

Commit 21554e8

Browse files
committed
o bring in Java 6's Collections.newSetFromMap as Generic.newSetFromMap which
makes our ConcurrentHashSet redundant o hold PyConnection cursors/statements in a weak Set so they can be GC'd easier. synchronize these sets too fixes #1582
1 parent 800cb0b commit 21554e8

File tree

4 files changed

+140
-122
lines changed

4 files changed

+140
-122
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Jython 2.5.2a1
2626
- [ 1502 ] string-escape codec incorrect
2727
- [ 1534 ] new style object __dict__[name] ignored
2828
- [ 1479 ] xml parser file lock
29+
- [ 1582 ] com.ziclix.python.sql.PyConnection leaks memory
2930
- Fix runtime issues during exitfuncs triggered via SystemRestart (such as
3031
during Django or Pylons development mode reloading)
3132
- Fix pickling of collections.defaultdict objects

src/com/ziclix/python/sql/PyConnection.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
import java.sql.Connection;
1212
import java.sql.SQLException;
1313
import java.util.Collections;
14-
import java.util.HashSet;
14+
import java.util.WeakHashMap;
1515
import java.util.Set;
1616

1717
import org.python.core.ClassDictInit;
18+
import org.python.core.ContextManager;
1819
import org.python.core.Py;
1920
import org.python.core.PyBuiltinMethodSet;
2021
import org.python.core.PyException;
@@ -23,11 +24,10 @@
2324
import org.python.core.PyObject;
2425
import org.python.core.PyString;
2526
import org.python.core.PyUnicode;
26-
27-
import com.ziclix.python.sql.util.PyArgParser;
28-
import org.python.core.ContextManager;
2927
import org.python.core.ThreadState;
28+
import org.python.util.Generic;
3029

30+
import com.ziclix.python.sql.util.PyArgParser;
3131

3232
/**
3333
* A connection to the database.
@@ -93,9 +93,11 @@ public class PyConnection extends PyObject implements ClassDictInit, ContextMana
9393
*/
9494
public PyConnection(Connection connection) throws SQLException {
9595
this.closed = false;
96-
this.cursors = new HashSet<PyCursor>();
96+
cursors = Generic.newSetFromMap(new WeakHashMap<PyCursor, Boolean>());
97+
cursors = Collections.synchronizedSet(cursors);
9798
this.connection = connection;
98-
this.statements = new HashSet<PyStatement>();
99+
statements = Generic.newSetFromMap(new WeakHashMap<PyStatement, Boolean>());
100+
statements = Collections.synchronizedSet(statements);
99101
this.supportsTransactions = this.connection.getMetaData().supportsTransactions();
100102
this.supportsMultipleResultSets =
101103
this.connection.getMetaData().supportsMultipleResultSets();

src/org/python/core/util/ConcurrentHashSet.java

Lines changed: 0 additions & 109 deletions
This file was deleted.

src/org/python/util/Generic.java

Lines changed: 131 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
package org.python.util;
22

3+
import java.io.IOException;
4+
import java.io.ObjectInputStream;
5+
import java.io.Serializable;
6+
import java.util.AbstractSet;
37
import java.util.ArrayList;
8+
import java.util.Collection;
49
import java.util.HashMap;
510
import java.util.HashSet;
11+
import java.util.Iterator;
612
import java.util.List;
713
import java.util.Map;
814
import java.util.Set;
915
import java.util.concurrent.ConcurrentHashMap;
1016
import java.util.concurrent.ConcurrentMap;
1117

12-
import org.python.core.util.ConcurrentHashSet;
13-
1418
/**
1519
* Static methods to make instances of collections with their generic types inferred from what
1620
* they're being assigned to. The idea is stolen from <code>Sets</code>, <code>Lists</code> and
@@ -64,8 +68,8 @@ public static <K, V> ConcurrentMap<K, V> concurrentMap() {
6468
/**
6569
* Makes a Set using the generic type inferred from whatever this is being assigned to.
6670
*/
67-
public static <T> Set<T> set() {
68-
return new HashSet<T>();
71+
public static <E> Set<E> set() {
72+
return new HashSet<E>();
6973
}
7074

7175
/**
@@ -84,8 +88,128 @@ public static <T, U extends T> Set<T> set(U...contents) {
8488
* Makes a Set, ensuring safe concurrent operations, using generic types inferred from
8589
* whatever this is being assigned to.
8690
*/
87-
public static <T> Set<T> concurrentSet() {
88-
return new ConcurrentHashSet<T>(CHM_INITIAL_CAPACITY, CHM_LOAD_FACTOR,
89-
CHM_CONCURRENCY_LEVEL);
91+
public static <E> Set<E> concurrentSet() {
92+
return newSetFromMap(new ConcurrentHashMap<E, Boolean>(CHM_INITIAL_CAPACITY,
93+
CHM_LOAD_FACTOR,
94+
CHM_CONCURRENCY_LEVEL));
95+
}
96+
97+
/**
98+
* Return a Set backed by the specified Map with the same ordering, concurrency and
99+
* performance characteristics.
100+
*
101+
* The specified Map must be empty at the time this method is invoked.
102+
*
103+
* Note that this method is based on Java 6's Collections.newSetFromMap, and will be
104+
* removed in a future version of Jython (likely 2.6) that will rely on Java 6.
105+
*
106+
* @param map the backing Map
107+
* @return a Set backed by the Map
108+
* @throws IllegalArgumentException if Map is not empty
109+
*/
110+
public static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {
111+
return new SetFromMap<E>(map);
112+
}
113+
114+
/**
115+
* A Set backed by a generic Map.
116+
*/
117+
private static class SetFromMap<E> extends AbstractSet<E>
118+
implements Serializable {
119+
120+
/** The backing Map. */
121+
private final Map<E, Boolean> map;
122+
123+
/** Backing's KeySet. */
124+
private transient Set<E> keySet;
125+
126+
public SetFromMap(Map<E, Boolean> map) {
127+
if (!map.isEmpty()) {
128+
throw new IllegalArgumentException("Map is non-empty");
129+
}
130+
this.map = map;
131+
keySet = map.keySet();
132+
}
133+
134+
@Override
135+
public int size() {
136+
return map.size();
137+
}
138+
139+
@Override
140+
public boolean isEmpty() {
141+
return map.isEmpty();
142+
}
143+
144+
@Override
145+
public boolean contains(Object o) {
146+
return map.containsKey(o);
147+
}
148+
149+
@Override
150+
public boolean containsAll(Collection<?> c) {
151+
return keySet.containsAll(c);
152+
}
153+
154+
@Override
155+
public Iterator<E> iterator() {
156+
return keySet.iterator();
157+
}
158+
159+
@Override
160+
public Object[] toArray() {
161+
return keySet.toArray();
162+
}
163+
164+
@Override
165+
public <T> T[] toArray(T[] a) {
166+
return keySet.toArray(a);
167+
}
168+
169+
@Override
170+
public boolean add(E e) {
171+
return map.put(e, Boolean.TRUE) == null;
172+
}
173+
174+
@Override
175+
public boolean remove(Object o) {
176+
return map.remove(o) != null;
177+
}
178+
179+
@Override
180+
public boolean removeAll(Collection<?> c) {
181+
return keySet.removeAll(c);
182+
}
183+
184+
@Override
185+
public boolean retainAll(Collection<?> c) {
186+
return keySet.retainAll(c);
187+
}
188+
189+
@Override
190+
public void clear() {
191+
map.clear();
192+
}
193+
194+
@Override
195+
public boolean equals(Object o) {
196+
return o == this || keySet.equals(o);
197+
}
198+
199+
@Override
200+
public int hashCode() {
201+
return keySet.hashCode();
202+
}
203+
204+
@Override
205+
public String toString() {
206+
return keySet.toString();
207+
}
208+
209+
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
210+
in.defaultReadObject();
211+
keySet = map.keySet();
212+
}
90213
}
214+
91215
}

0 commit comments

Comments
 (0)