Skip to content

Commit

Permalink
Remove memory leak on resetDriver() when handle TERM signal enabled
Browse files Browse the repository at this point in the history
fixes #14
  • Loading branch information
sarxos committed Jan 8, 2013
1 parent 421c070 commit 5a50ddd
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 50 deletions.
33 changes: 30 additions & 3 deletions webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

Expand Down Expand Up @@ -58,7 +59,7 @@ public void run() {
}
}

private static final class WebcamsDiscovery implements Callable<List<Webcam>> {
private static final class WebcamsDiscovery implements Callable<List<Webcam>>, ThreadFactory {

private final WebcamDriver driver;

Expand All @@ -74,6 +75,13 @@ public List<Webcam> call() throws Exception {
}
return webcams;
}

@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "webcam-discovery");
t.setDaemon(true);
return t;
}
}

/**
Expand Down Expand Up @@ -180,6 +188,7 @@ public synchronized void close() {
}

Runtime.getRuntime().removeShutdownHook(hook);

close0();
}

Expand Down Expand Up @@ -362,8 +371,10 @@ public static List<Webcam> getWebcams(long timeout) throws TimeoutException {
driver = new WebcamDefaultDriver();
}

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<List<Webcam>> future = executor.submit(new WebcamsDiscovery(driver));
WebcamsDiscovery discovery = new WebcamsDiscovery(driver);
ExecutorService executor = Executors.newSingleThreadExecutor(discovery);
Future<List<Webcam>> future = executor.submit(discovery);

executor.shutdown();

try {
Expand Down Expand Up @@ -537,7 +548,14 @@ public static void resetDriver() {

driver = null;

if (deallocOnTermSignal) {
WebcamDeallocator.unstore();
}

if (webcams != null && !webcams.isEmpty()) {
for (Webcam webcam : webcams) {
webcam.dispose();
}
webcams.clear();
}

Expand Down Expand Up @@ -586,12 +604,21 @@ public WebcamDevice getDevice() {
*/
protected void dispose() {

LOG.info("Disposing webcam {}", getName());

// hook can be null because there is a possibility that webcam has never
// been open and therefore hook was not created
if (hook != null) {
Runtime.getRuntime().removeShutdownHook(hook);
}

open = false;
disposed = true;

WebcamEvent we = new WebcamEvent(this);
for (WebcamListener l : listeners) {
try {
l.webcamClosed(we);
l.webcamDisposed(we);
} catch (Exception e) {
LOG.error(String.format("Notify webcam disposed, exception when calling %s listener", l.getClass()), e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.Observable;
import java.util.Observer;


/**
Expand All @@ -13,10 +11,11 @@
*
* @author Bartosz Firyn (SarXos)
*/
class WebcamDeallocator implements Observer {
final class WebcamDeallocator {

private Webcam[] webcams = null;
private WebcamSignalHandler handler = new WebcamSignalHandler();
private static final WebcamSignalHandler HANDLER = new WebcamSignalHandler();

private final Webcam[] webcams;

/**
* This constructor is used internally to create new deallocator for the
Expand All @@ -25,43 +24,48 @@ class WebcamDeallocator implements Observer {
* @param devices the devices to be stored in deallocator
*/
private WebcamDeallocator(Webcam[] devices) {
if (devices != null && devices.length > 0) {
this.webcams = devices;
this.handler.listen("TERM", this);
}
this.webcams = devices;
}

/**
* Store devices to be deallocated when TERM signal has been received.
*
* @param devices the devices array to be stored by deallocator
* @param webcams the webcams array to be stored in deallocator
*/
protected static final void store(Webcam[] devices) {
new WebcamDeallocator(devices);
protected static final void store(Webcam[] webcams) {
if (HANDLER.get() == null) {
HANDLER.set(new WebcamDeallocator(webcams));
} else {
throw new IllegalStateException("Deallocator is already set!");
}
}

@Override
public void update(Observable observable, Object object) {
for (Webcam device : webcams) {
protected static final void unstore() {
HANDLER.reset();
}

protected void deallocate() {
for (Webcam w : webcams) {
try {
device.dispose();
w.dispose();
} catch (Throwable t) {
caugh(t);
}
}
}

public void caugh(Throwable e) {
private void caugh(Throwable t) {
File f = new File(String.format("webcam-capture-hs-%s", System.currentTimeMillis()));
PrintStream ps = null;
try {
e.printStackTrace(ps = new PrintStream(f));
} catch (FileNotFoundException e2) {
// ignore, stdout is not working, cannot do anything more
t.printStackTrace(ps = new PrintStream(f));
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.github.sarxos.webcam;

import java.util.Observable;
import java.util.Observer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sun.misc.Signal;
import sun.misc.SignalHandler;
Expand All @@ -14,29 +14,44 @@
* @author Bartosz Firyn (SarXos)
*/
@SuppressWarnings("restriction")
class WebcamSignalHandler extends Observable implements SignalHandler {
final class WebcamSignalHandler implements SignalHandler {

private static final Logger LOG = LoggerFactory.getLogger(WebcamSignalHandler.class);

private WebcamDeallocator deallocator = null;

private SignalHandler handler = null;

public void listen(String signal, Observer observer) throws IllegalArgumentException {
addObserver(observer);
handler = Signal.handle(new Signal(signal), this);
public WebcamSignalHandler() {
handler = Signal.handle(new Signal("TERM"), this);
}

@Override
public void handle(Signal signal) {

LOG.warn("Detected signal {} {}, calling deallocator", signal.getName(), signal.getNumber());

// do nothing on "signal default" or "signal ignore"
if (handler == SIG_DFL || handler == SIG_IGN) {
return;
}

setChanged();

try {
notifyObservers(signal);
deallocator.deallocate();
} finally {
handler.handle(signal);
}
}

public void set(WebcamDeallocator deallocator) {
this.deallocator = deallocator;
}

public WebcamDeallocator get() {
return this.deallocator;
}

public void reset() {
this.deallocator = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -37,28 +36,20 @@ public class WebcamDefaultDriver implements WebcamDriver {
*/
private static final GetDevicesTask DEVICES_TASK = new GetDevicesTask(processor);

/**
* Static devices list.
*/
private static final List<WebcamDevice> devices = new ArrayList<WebcamDevice>();

private static final AtomicBoolean initialized = new AtomicBoolean(false);

@Override
public List<WebcamDevice> getDevices() {

if (initialized.compareAndSet(false, true)) {
LOG.debug("Searching devices");

LOG.debug("Searching devices");
List<WebcamDevice> devices = new ArrayList<WebcamDevice>();

for (Device device : DEVICES_TASK.getDevices()) {
devices.add(new WebcamDefaultDevice(device));
}
for (Device device : DEVICES_TASK.getDevices()) {
devices.add(new WebcamDefaultDevice(device));
}

if (LOG.isDebugEnabled()) {
for (WebcamDevice device : devices) {
LOG.debug("Found device " + device);
}
if (LOG.isDebugEnabled()) {
for (WebcamDevice device : devices) {
LOG.debug("Found device {}", device.getName());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
*
* @author Bartosz Firyn (SarXos)
*/
public class WebcamGrabberProcessor {
public final class WebcamGrabberProcessor {

/**
* Thread factory for processor.
Expand Down

0 comments on commit 5a50ddd

Please sign in to comment.