forked from qzind/tray
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Printer Status: Performance improvements (qzind#1124)
Caching for CUPS status performance (#6) --------- Co-authored-by: Vzor- <[email protected]>
- Loading branch information
Showing
8 changed files
with
348 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package qz.common; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
import java.util.function.Supplier; | ||
|
||
/** | ||
* A generic class that encapsulates an object for caching. The cached object | ||
* will be refreshed automatically when accessed after its lifespan has expired. | ||
* | ||
* @param <T> The type of object to be cached. | ||
*/ | ||
public class CachedObject<T> { | ||
public static final long DEFAULT_LIFESPAN = 5000; // in milliseconds | ||
T lastObject; | ||
Supplier<T> supplier; | ||
private long timestamp; | ||
private long lifespan; | ||
|
||
/** | ||
* Creates a new CachedObject with a default lifespan of 5000 milliseconds | ||
* | ||
* @param supplier The function to pull new values from | ||
*/ | ||
public CachedObject(Supplier<T> supplier) { | ||
this(supplier, DEFAULT_LIFESPAN); | ||
} | ||
|
||
/** | ||
* Creates a new CachedObject | ||
* | ||
* @param supplier The function to pull new values from | ||
* @param lifespan The lifespan of the cached object in milliseconds | ||
*/ | ||
public CachedObject(Supplier<T> supplier, long lifespan) { | ||
this.supplier = supplier; | ||
setLifespan(lifespan); | ||
timestamp = Long.MIN_VALUE; // System.nanoTime() can be negative, MIN_VALUE guarantees a first-run. | ||
} | ||
|
||
/** | ||
* Registers a new supplier for the CachedObject | ||
* | ||
* @param supplier The function to pull new values from | ||
*/ | ||
@SuppressWarnings("unused") | ||
public void registerSupplier(Supplier<T> supplier) { | ||
this.supplier = supplier; | ||
} | ||
|
||
/** | ||
* Sets the lifespan of the cached object | ||
* | ||
* @param milliseconds The lifespan of the cached object in milliseconds | ||
*/ | ||
public void setLifespan(long milliseconds) { | ||
lifespan = Math.max(0, milliseconds); // prevent overflow | ||
} | ||
|
||
/** | ||
* Retrieves the cached object. | ||
* If the cached object's lifespan has expired, it gets refreshed before being returned. | ||
* | ||
* @return The cached object | ||
*/ | ||
public T get() { | ||
return get(false); | ||
} | ||
|
||
/** | ||
* Retrieves the cached object. | ||
* If the cached object's lifespan is expired or forceRefresh is true, it gets refreshed before being returned. | ||
* | ||
* @param forceRefresh If true, the cached object will be refreshed before being returned regardless of its lifespan | ||
* @return The cached object | ||
*/ | ||
public T get(boolean forceRefresh) { | ||
long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); | ||
// check lifespan | ||
if (forceRefresh || (timestamp + lifespan <= now)) { | ||
timestamp = now; | ||
lastObject = supplier.get(); | ||
} | ||
return lastObject; | ||
} | ||
|
||
// Test | ||
public static void main(String ... args) throws InterruptedException { | ||
final AtomicInteger testInt = new AtomicInteger(0); | ||
|
||
CachedObject<Integer> cachedString = new CachedObject<>(testInt::incrementAndGet); | ||
for(int i = 0; i < 100; i++) { | ||
Thread.sleep(1500); | ||
System.out.println(cachedString.get()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package qz.printer.info; | ||
|
||
import qz.common.CachedObject; | ||
|
||
import javax.print.*; | ||
import javax.print.attribute.Attribute; | ||
import javax.print.attribute.AttributeSet; | ||
import javax.print.attribute.PrintServiceAttribute; | ||
import javax.print.attribute.PrintServiceAttributeSet; | ||
import javax.print.event.PrintServiceAttributeListener; | ||
import java.util.HashMap; | ||
import java.util.function.Supplier; | ||
|
||
/** | ||
* <code>PrintService.getName()</code> is slow, and gets increasingly slower the more times it's called. | ||
* | ||
* By overriding and caching the <code>PrintService</code> attributes, we're able to help suppress/delay the | ||
* performance loss of this bug. | ||
* | ||
* See also JDK-7001133 | ||
*/ | ||
public class CachedPrintService implements PrintService { | ||
private final PrintService printService; | ||
private final long lifespan; | ||
private final CachedObject<String> cachedName; | ||
private final CachedObject<PrintServiceAttributeSet> cachedAttributeSet; | ||
private final HashMap<Class<?>, CachedObject<?>> cachedAttributes = new HashMap<>(); | ||
|
||
public CachedPrintService(PrintService printService, long lifespan) { | ||
this.printService = printService; | ||
this.lifespan = lifespan; | ||
cachedName = new CachedObject<>(this.printService::getName, lifespan); | ||
cachedAttributeSet = new CachedObject<>(this.printService::getAttributes, lifespan); | ||
} | ||
|
||
public CachedPrintService(PrintService printService) { | ||
this(printService, CachedObject.DEFAULT_LIFESPAN); | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return cachedName.get(); | ||
} | ||
|
||
@Override | ||
public DocPrintJob createPrintJob() { | ||
return printService.createPrintJob(); | ||
} | ||
|
||
@Override | ||
public void addPrintServiceAttributeListener(PrintServiceAttributeListener listener) { | ||
printService.addPrintServiceAttributeListener(listener); | ||
} | ||
|
||
@Override | ||
public void removePrintServiceAttributeListener(PrintServiceAttributeListener listener) { | ||
printService.removePrintServiceAttributeListener(listener); | ||
} | ||
|
||
@Override | ||
public PrintServiceAttributeSet getAttributes() { | ||
return cachedAttributeSet.get(); | ||
} | ||
|
||
public PrintService getJavaxPrintService() { | ||
return printService; | ||
} | ||
|
||
@Override | ||
public <T extends PrintServiceAttribute> T getAttribute(Class<T> category) { | ||
if (!cachedAttributes.containsKey(category)) { | ||
Supplier<T> supplier = () -> printService.getAttribute(category); | ||
CachedObject<T> cachedObject = new CachedObject<>(supplier, lifespan); | ||
cachedAttributes.put(category, cachedObject); | ||
} | ||
return category.cast(cachedAttributes.get(category).get()); | ||
} | ||
|
||
@Override | ||
public DocFlavor[] getSupportedDocFlavors() { | ||
return printService.getSupportedDocFlavors(); | ||
} | ||
|
||
@Override | ||
public boolean isDocFlavorSupported(DocFlavor flavor) { | ||
return printService.isDocFlavorSupported(flavor); | ||
} | ||
|
||
@Override | ||
public Class<?>[] getSupportedAttributeCategories() { | ||
return printService.getSupportedAttributeCategories(); | ||
} | ||
|
||
@Override | ||
public boolean isAttributeCategorySupported(Class<? extends Attribute> category) { | ||
return printService.isAttributeCategorySupported(category); | ||
} | ||
|
||
@Override | ||
public Object getDefaultAttributeValue(Class<? extends Attribute> category) { | ||
return printService.getDefaultAttributeValue(category); | ||
} | ||
|
||
@Override | ||
public Object getSupportedAttributeValues(Class<? extends Attribute> category, DocFlavor flavor, AttributeSet attributes) { | ||
return printService.getSupportedAttributeValues(category, flavor, attributes); | ||
} | ||
|
||
@Override | ||
public boolean isAttributeValueSupported(Attribute attrval, DocFlavor flavor, AttributeSet attributes) { | ||
return printService.isAttributeValueSupported(attrval, flavor, attributes); | ||
} | ||
|
||
@Override | ||
public AttributeSet getUnsupportedAttributes(DocFlavor flavor, AttributeSet attributes) { | ||
return printService.getUnsupportedAttributes(flavor, attributes); | ||
} | ||
|
||
@Override | ||
public ServiceUIFactory getServiceUIFactory() { | ||
return printService.getServiceUIFactory(); | ||
} | ||
|
||
public boolean equals(Object obj) { | ||
return (obj == this || | ||
(obj instanceof PrintService && | ||
((PrintService)obj).getName().equals(getName()))); | ||
} | ||
|
||
public int hashCode() { | ||
return this.getClass().hashCode()+getName().hashCode(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package qz.printer.info; | ||
|
||
import qz.common.CachedObject; | ||
|
||
import javax.print.PrintService; | ||
import javax.print.PrintServiceLookup; | ||
|
||
/** | ||
* PrintService[] cache to workaround JDK-7001133 | ||
* | ||
* See also <code>CachedPrintService</code> | ||
*/ | ||
public class CachedPrintServiceLookup { | ||
private static final CachedObject<CachedPrintService> cachedDefault = new CachedObject<>(CachedPrintServiceLookup::wrapDefaultPrintService); | ||
private static final CachedObject<CachedPrintService[]> cachedPrintServices = new CachedObject<>(CachedPrintServiceLookup::wrapPrintServices); | ||
|
||
// Keep CachedPrintService object references between calls to supplier | ||
private static CachedPrintService[] cachedPrintServicesCopy = {}; | ||
|
||
static { | ||
setLifespan(CachedObject.DEFAULT_LIFESPAN); | ||
} | ||
|
||
public static PrintService lookupDefaultPrintService() { | ||
return cachedDefault.get(); | ||
} | ||
|
||
public static void setLifespan(long lifespan) { | ||
cachedDefault.setLifespan(lifespan); | ||
cachedPrintServices.setLifespan(lifespan); | ||
} | ||
|
||
public static PrintService[] lookupPrintServices() { | ||
return cachedPrintServices.get(); | ||
} | ||
|
||
private static CachedPrintService wrapDefaultPrintService() { | ||
PrintService javaxPrintService = PrintServiceLookup.lookupDefaultPrintService(); | ||
// CachedObject's supplier returns null | ||
if(javaxPrintService == null) { | ||
return null; | ||
} | ||
// If this CachedPrintService already exists, reuse it rather than wrapping a new one | ||
CachedPrintService cachedPrintService = getMatch(cachedPrintServicesCopy, javaxPrintService); | ||
if (cachedPrintService == null) { | ||
// Wrap a new one | ||
cachedPrintService = new CachedPrintService(javaxPrintService); | ||
} | ||
return cachedPrintService; | ||
} | ||
|
||
private static CachedPrintService[] wrapPrintServices() { | ||
PrintService[] javaxPrintServices = PrintServiceLookup.lookupPrintServices(null, null); | ||
CachedPrintService[] cachedPrintServices = new CachedPrintService[javaxPrintServices.length]; | ||
for (int i = 0; i < javaxPrintServices.length; i++) { | ||
// If this CachedPrintService already exists, reuse it rather than wrapping a new one | ||
cachedPrintServices[i] = getMatch(cachedPrintServicesCopy, javaxPrintServices[i]); | ||
if (cachedPrintServices[i] == null) { | ||
cachedPrintServices[i] = new CachedPrintService(javaxPrintServices[i]); | ||
} | ||
} | ||
cachedPrintServicesCopy = cachedPrintServices; | ||
// Immediately invalidate the defaultPrinter cache | ||
cachedDefault.get(true); | ||
|
||
return cachedPrintServices; | ||
} | ||
|
||
private static CachedPrintService getMatch(CachedPrintService[] array, PrintService javaxPrintService) { | ||
if(array != null) { | ||
for(CachedPrintService cps : array) { | ||
// Note: Order of operations can cause the defaultService pointer to differ if lookupDefaultPrintService() | ||
// is called before lookupPrintServices(...) because the provider will invalidate on refreshServices() if | ||
// "sun.java2d.print.polling" is set to "false". We're OK with this because worst case, we just | ||
// call "lpstat" a second time. | ||
if (cps.getJavaxPrintService() == javaxPrintService) return cps; | ||
} | ||
} | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.