This is an automated email from the git hooks/post-receive script. apoikos pushed a commit to branch master in repository beckon-clojure.
commit 7a3915c4985eb8d5a18b7fef2fcf203fc736827d Author: Apollon Oikonomopoulos <[email protected]> Date: Fri Aug 4 18:21:59 2017 -0400 New upstream version 0.1.1 --- .gitignore | 13 ++ CHANGES.md | 18 ++ LICENSE | 198 +++++++++++++++++++++ README.md | 150 ++++++++++++++++ doc/intro.md | 3 + project.clj | 13 ++ src/clojure/beckon.clj | 45 +++++ src/java/com/hypirion/beckon/SignalAtoms.java | 97 ++++++++++ src/java/com/hypirion/beckon/SignalFolder.java | 42 +++++ .../beckon/SignalHandlerNotFoundException.java | 9 + src/java/com/hypirion/beckon/SignalRegisterer.java | 55 ++++++ .../hypirion/beckon/SignalRegistererHelper.java | 144 +++++++++++++++ test/beckon/core_test.clj | 7 + 13 files changed, 794 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8bea18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/target +/lib +/classes +/checkouts +pom.xml +pom.xml.asc +*.jar +*.class +.lein-deps-sum +.lein-failures +.lein-plugins +.lein-repl-history +*~ diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..3fc3bcb --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,18 @@ +# beckon changelog + +## 0.1.1 [`docs`][0.1.0-docs] [`tag`][0.1.1-tag] + +* Beckon will now always compile to 1.6-compliant bytecode. + +## 0.1.0 [`docs`][0.1.0-docs] [`tag`][0.1.0-tag] + +* **New:** The function `signal-atom` returns an atom containing a collection of + functions. The functions will be invoked sequentially whenever a signal is + trapped. +* **New:** The function `raise!` raises a signal. +* **New:** `reinit!` and `reinit-all!` reinitializes signal handlers, and return + them to their "factory settings". + +[0.1.1-tag]: https://github.com/hyPiRion/beckon/tree/0.1.1 +[0.1.0-tag]: https://github.com/hyPiRion/beckon/tree/0.1.0 +[0.1.0-docs]: http://hypirion.github.com/beckon/0.1.0/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..11ecb79 --- /dev/null +++ b/LICENSE @@ -0,0 +1,198 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' from + a Contributor if it was added to the Program by such Contributor itself or + anyone acting on such Contributor's behalf. Contributions do not include + additions to the Program which: (i) are separate modules of software + distributed in conjunction with the Program under their own license + agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly perform, + distribute and sublicense the Contribution of such Contributor, if any, and + such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of the + Contribution and the Program if, at the time the Contribution is added by + the Contributor, such addition of the Contribution causes such combination + to be covered by the Licensed Patents. The patent license shall not apply + to any other combinations which include the Contribution. No hardware per + se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses to + its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other entity + based on infringement of intellectual property rights or otherwise. As a + condition to exercising the rights and licenses granted hereunder, each + Recipient hereby assumes sole responsibility to secure any other + intellectual property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to distribute the Program, it + is Recipient's responsibility to acquire that license before distributing + the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its +own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties and + conditions, express and implied, including warranties or conditions of + title and non-infringement, and implied warranties or conditions of + merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are offered + by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained within + the Program. + +Each Contributor must identify itself as the originator of its Contribution, if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, if +a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, damages +and costs (collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to the +extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor to +control, and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may participate in +any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If that +Commercial Contributor then makes performance claims, or offers warranties +related to Product X, those performance claims and warranties are such +Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a court +requires any other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with its exercise of +rights under this Agreement , including but not limited to the risks and costs +of program errors, compliance with applicable laws, damage to or loss of data, +programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, such +provision shall be reformed to the minimum extent necessary to make such +provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted under +Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue and +survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to time. +No one other than the Agreement Steward has the right to modify this Agreement. +The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation +may assign the responsibility to serve as the Agreement Steward to a suitable +separate entity. Each new version of the Agreement will be given a +distinguishing version number. The Program (including Contributions) may always +be distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to distribute the Program (including its Contributions) +under the new version. Except as expressly stated in Sections 2(a) and 2(b) +above, Recipient receives no rights or licenses to the intellectual property of +any Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted under +this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2df3c05 --- /dev/null +++ b/README.md @@ -0,0 +1,150 @@ +# beckon + +A Clojure library to handle POSIX signals in JVM applications with style and +grace. Sets up with the dirty parts and let you work with it in a (relatively) +simple fashion. + +## Quick-start + +Add the following dependency to your `project.clj` file: + +```clj +[beckon "0.1.1"] +``` + +Say you want to grab `SIGINT` and, say, print "Hahah, nothing can stop me!" +whenever someone attempts to interrupt the process. Hit up your Emacs nREPL +through `nrepl-jack-in` or similar methods, then write the following: + +```clj +(require 'beckon) + +(let [print-function (fn [] (println "Hahah, nothing can stop me!"))] + (reset! (beckon/signal-atom "INT") #{print-function})) +``` + +That's it—it's not harder than that. To confirm whether this works or not, we +can use the `raise!` function, which will raise a POSIX signal to the VM: + +```clj +(beckon/raise! "INT") +; prints nothing +``` + +So why didn't `raise!` print out anything? Whenever the JVM receives a signal, +it decides to start up a new thread with maximum priority and do the signal +handling asynchronously. As such, it will not show in the nrepl window. Move +over to the `*nrepl-server*` buffer instead, and you'll see the message. + +By default, things like SIGTERM and SIGINT will terminate the running VM, so be +a bit careful. We can of course play around with it in a REPL: + +```clj +(beckon/raise! "TERM") +; NB: This will terminate nREPL. +``` + +And if you somehow managed to screw up the signal handling and want to go back +to the default, that's possible too: + +```clj +(beckon/reinit! "INT") +; Reinitializes the SIGINT signal handler. +(beckon/raise! "INT") +; NB: This will terminate your JVM process. +``` + +And well, that's really all you need to know in order to work with beckon. + +## Usage + +The core of beckon consist of 4 functions: `signal-atom`, `raise!`, `reinit!` +and `reinit-all!`. `signal-atom` is the only one needed in production systems, +usually. The other functions help out with debugging and resetting signal +handling back to "factory settings"—the initial setup of signal handlers when +the JVM starts up. + +### `signal-atom` + +`signal-atom` is the core piece of this library and (ab)uses atoms to setup +signal handlers for Clojurians. As you'd guess, this returns an atom. The atom +has a validator function attached to it, so only Seqable collections where every +element are Runnable are legal values in this atom. All Clojure functions +implements Runnable, but only functions which has a zero-argument invokation +will actually work as a Runnable. + +Beckon require the contents of the atom to be a Seqable of Runnable because it +makes it possible to add multiple independend signal handlers to a single +signal. The signal handlers will always be executed sequentially, the only +exception is if one of the functions throw an exception or error. If a function +throws an exception, the signal handling will be cancelled (but no exception +will be thrown), and if a function throws an error, the whole signal handling +crashes. You can in theory "abuse" it to get conditional function dispatching. +For example: + +```clj +(reset! (beckon/signal-atom "INT") + [(fn [] (println "foo")) + (fn [] (println "bar") (throw (Exception.))) + (fn [] (println "We'll never see this"))]) +``` + +Will only print `foo` and `bar`. + +It's not really a good way to do dispatching though, so you're advised to do +this kind of logic within functions whenever possible. + +The signal handler is automatically updated whenever the atom is updated, but +not vice versa. So if you use beckon, please don't try and hack in signal +handling through another library or through the native java interface. + +### `raise!` + +`raise!` is probably the most understandable function in the system. It will +send off a signal of the type given as input. For instance, `(beckon/raise! +"INT")` will act as if a SIGINT signal was sent to the JVM process. It's handy +to check out that your signal handlers work as intended. + +### `reinit!` and `reinit-all!` + +`reinit!` and `reinit-all!` are functions which reset the signal handlers back +to their original state when the JVM was started. `reinit!` takes a single +argument, the signal to reset, whereas `reinit-all!` takes zero and resets every +single one. + +## How is a signal handled? + +In the JVM, whenever a signal is received, the VM starts up a new thread at +`Thread.MAX_PRIORITY`, and executes it asynchronously. That's why we don't see +any printing in nREPL, although it should work fine in command-line programs. I +would recommend, however, to have some sort of logger or printer a signal +handler sends a message to, instead of having the signal handler printing +manually. + +## "FAQ" + +People using this library may get some issues when using it. If this list of +common problems doesn't help you, please add a [new issue][new-issue] and we'll +see what we can do about it! + +**Q:** My infinite sequence doesn't work with this library, why is that? +**A:** This library is designed to be easy to use for Clojure developers, + without sacrificing speed. As such, the collection of functions are realized + within Beckon and stored within a Java array. An infinite sequence doesn't fit + in a Java array, sadly. + +**Q:** For some reason, keywords, symbols and other things which are clearly not + functions are allowed in the collection of functions! Why is that? +**A:** Keywords, symbols and some persistent collections implement the `IFn` + interface in Clojure, which automatically means that they do in fact implement + Runnable. But, as mentioned, while they still implement Runnable, that won't + mean they are actually able to send back something of value. This is intended + to be fixed in a later version. + +[new-issue]: https://github.com/hyPiRion/beckon/issues/new "Add a new issue to Beckon" + +## License + +Copyright © 2013 Jean Niklas L'orange + +Distributed under the Eclipse Public License, the same as Clojure. diff --git a/doc/intro.md b/doc/intro.md new file mode 100644 index 0000000..5b3f915 --- /dev/null +++ b/doc/intro.md @@ -0,0 +1,3 @@ +# Introduction to beckon + +TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..a31d7dd --- /dev/null +++ b/project.clj @@ -0,0 +1,13 @@ +(defproject beckon "0.1.1" + :description "Handle POSIX signals in Clojure with style and grace." + :url "https://github.com/hyPiRion/beckon" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :dependencies [[org.clojure/clojure "1.5.1"]] + :source-paths ["src/clojure"] + :java-source-paths ["src/java"] + :javac-options ["-target" "1.6" "-source" "1.6" "-Xlint:-options"] + :deploy-branches ["stable"] + :profiles {:dev {:plugins [[codox "0.6.4"]] + :codox {:sources ["src/clojure"] + :output-dir "codox"}}}) diff --git a/src/clojure/beckon.clj b/src/clojure/beckon.clj new file mode 100644 index 0000000..6913d11 --- /dev/null +++ b/src/clojure/beckon.clj @@ -0,0 +1,45 @@ +(ns beckon + (:import (com.hypirion.beckon SignalAtoms SignalRegisterer))) + +(defn signal-atom + "Returns the beckon atom of the signal with the name signal-name. The changes + in the atom returned is reflected back to the signal handling, but NOT vice + versa. Multiple calls for the same signal atom will return the + same (identical) atom. + + A beckon atom is an atom containing a Seqable Clojure collection, where all + the elements in the Seqable collection must be Runnable. Clojure functions are + by default Runnable if they can take zero arguments. By default would a beckon + atom be a Clojure Set with the default signal handler wrapped within a + Runnable. Note that with sets, the ordering of the Runnable is arbitrary; If + you need ordered calling, convert the set to a vector or list first. + + The signal handling of a (modified) beckon atom is done by simply calling all + the functions in order. If the Runnable throws an Exception, the handling will + stop and no more elements will be called. If the signal handler throws an + Error, that Error will not be caught. + + signal-name must be a legal POSIX signal, where SIG is omitted from the first + part of the name." + [signal-name] + (SignalAtoms/getSignalAtom signal-name)) + +(defn raise! + "Raises a signal of the type specified. Consequently, the signal will also be + handled by the signal handling procedure. + + signal-name must be a legal POSIX signal, where SIG is omitted from the first + part of the name." + [signal-name] + (SignalRegisterer/raiseSignal signal-name)) + +(defn reinit! + "Reinitialises the signal handler to the signal name, just as it were at the + start of this JVM process." + [signal-name] + (SignalRegisterer/resetDefaultHandler signal-name)) + +(defn reinit-all! + "Reintializes all signal handlers handled by beckon to their default values." + [] + (SignalRegisterer/resetAllHandlers)) diff --git a/src/java/com/hypirion/beckon/SignalAtoms.java b/src/java/com/hypirion/beckon/SignalAtoms.java new file mode 100644 index 0000000..19c7eae --- /dev/null +++ b/src/java/com/hypirion/beckon/SignalAtoms.java @@ -0,0 +1,97 @@ +package com.hypirion.beckon; + +import java.util.Map; +import java.util.HashMap; + +import clojure.lang.Atom; +import clojure.lang.PersistentHashMap; +import clojure.lang.Keyword; +import clojure.lang.AFn; +import clojure.lang.IFn; +import clojure.lang.Seqable; +import clojure.lang.ISeq; + +public class SignalAtoms { + private static final Map<String, Atom> atoms = new HashMap<String, Atom>(); + + /** + * The keyword <code>:signal</code>. + */ + public static final Keyword SIGNAL = Keyword.intern("signal"); + + /** + * A standard Atom validator function which tests whether the new value is a + * Seqable, where each element in the Seqable implements Runnable. + */ + public static final IFn SIGNAL_ATOM_VALIDATOR = new SignalAtomValidator(); + + /** + * Returns a Clojure Atom containing a Seqable. The Seqable represents a + * list of functions where the functions are called in order whenever a + * Signal by the type <code>signame</code> is received by this process. + * + * @exception SignalHandlerNotFoundException if this code is unable to + * detect a SignalHandler and/or a Signal class. + */ + public static final synchronized Atom getSignalAtom(String signame) + throws SignalHandlerNotFoundException{ + if (!atoms.containsKey(signame)) { + Object list; + try { + list = SignalRegistererHelper.getHandlerSeq(signame); + } + catch (LinkageError le) { + throw new SignalHandlerNotFoundException(); + } + PersistentHashMap metadata = PersistentHashMap.create(SIGNAL, signame); + Atom atm = new Atom(list, metadata); + atm.setValidator(SIGNAL_ATOM_VALIDATOR); + SignalAtomWatch saw = new SignalAtomWatch(signame); + atm.addWatch(signame, saw); + atoms.put(signame, atm); + } + return atoms.get(signame); + } + + private static class SignalAtomValidator extends AFn { + @Override + public Object invoke(Object newVal) { + if (newVal instanceof Seqable) { + ISeq seq = ((Seqable) newVal).seq(); + // An empty seqable returns null. + if (seq == null) { + return true; + } + int count = seq.count(); + while (count --> 0) { + Object o = seq.first(); + seq = seq.next(); + if (!(o instanceof Runnable)) { + return false; + } + } + return true; + } + else if (newVal == null) { + return true; + } + else { + return false; + } + } + } + + private static class SignalAtomWatch extends AFn { + public final String signame; + + public SignalAtomWatch(String signame) { + this.signame = signame; + } + + @Override + public Object invoke(Object key, Object ref, Object oldState, Object newState) { + SignalRegistererHelper.register(signame, (Seqable) newState); + return null; + } + } +} diff --git a/src/java/com/hypirion/beckon/SignalFolder.java b/src/java/com/hypirion/beckon/SignalFolder.java new file mode 100644 index 0000000..fb43152 --- /dev/null +++ b/src/java/com/hypirion/beckon/SignalFolder.java @@ -0,0 +1,42 @@ +package com.hypirion.beckon; + +import sun.misc.Signal; +import sun.misc.SignalHandler; + +import java.util.List; + +import clojure.lang.Seqable; +import clojure.lang.ISeq; + +public class SignalFolder implements SignalHandler { + final Seqable originalList; + final private Runnable[] fns; + + public SignalFolder(Seqable funs) { + ISeq seq = funs.seq(); + // seq may be null + if (seq == null) { + fns = new Runnable[0]; + } + else { + fns = new Runnable[seq.count()]; + for (int i = 0; i < fns.length; i++) { + fns[i] = (Runnable) seq.first(); + seq = seq.next(); + } + } + originalList = funs; + } + + public void handle(Signal sig) { + for (Runnable r : fns) { + boolean cont = true; + try { + r.run(); + } + catch (Exception e) { + break; + } + } + } +} diff --git a/src/java/com/hypirion/beckon/SignalHandlerNotFoundException.java b/src/java/com/hypirion/beckon/SignalHandlerNotFoundException.java new file mode 100644 index 0000000..b6d129b --- /dev/null +++ b/src/java/com/hypirion/beckon/SignalHandlerNotFoundException.java @@ -0,0 +1,9 @@ +package com.hypirion.beckon; + +class SignalHandlerNotFoundException extends Exception { + public SignalHandlerNotFoundException() { + super("SignalHandler was not found on this JVM -- please report an" + + "issue at beckon's github page with which JVM and what version" + + "you're using."); + } +} diff --git a/src/java/com/hypirion/beckon/SignalRegisterer.java b/src/java/com/hypirion/beckon/SignalRegisterer.java new file mode 100644 index 0000000..a941c92 --- /dev/null +++ b/src/java/com/hypirion/beckon/SignalRegisterer.java @@ -0,0 +1,55 @@ +package com.hypirion.beckon; + +public class SignalRegisterer { + + /** + * Resets the default handler of the Signal with the name + * <code>signame</code> to its original value. + * + * @exception SignalHandlerNotFoundException if this code is unable to + * detect a SignalHandler and/or a Signal class. + */ + public static void resetDefaultHandler(String signame) + throws SignalHandlerNotFoundException{ + try { + SignalRegistererHelper.resetDefaultHandler(signame); + } + catch (LinkageError le) { + throw new SignalHandlerNotFoundException(); + } + } + + /** + * Resets all signal handlers to their original value. + * + * @exception SignalHandlerNotFoundException if this code is unable to + * detect a SignalHandler and/or a Signal class. + */ + public static void resetAllHandlers() + throws SignalHandlerNotFoundException{ + try { + SignalRegistererHelper.resetAll(); + } + catch (LinkageError le) { + throw new SignalHandlerNotFoundException(); + } + } + + /** + * Raises a Signal with the name <code>signame</code> in the current + * process. Will consequently call the current SignalHandler for + * <code>signame</code> in another Thread with maximal priority. + * + * @exception SignalHandlerNotFoundException if this code is unable to + * detect a SignalHandler and/or a Signal class. + */ + public static void raiseSignal(String signame) + throws SignalHandlerNotFoundException { + try { + SignalRegistererHelper.raise(signame); + } + catch (LinkageError le) { + throw new SignalHandlerNotFoundException(); + } + } +} diff --git a/src/java/com/hypirion/beckon/SignalRegistererHelper.java b/src/java/com/hypirion/beckon/SignalRegistererHelper.java new file mode 100644 index 0000000..730f61b --- /dev/null +++ b/src/java/com/hypirion/beckon/SignalRegistererHelper.java @@ -0,0 +1,144 @@ +package com.hypirion.beckon; + +import sun.misc.Signal; +import sun.misc.SignalHandler; + +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; + +import clojure.lang.PersistentHashSet; +import clojure.lang.Seqable; + +public class SignalRegistererHelper { + + /** + * A set of modified signal handlers. + */ + private final static Map<String, SignalHandler> originalHandlers = + new HashMap<String, SignalHandler>(); + + /** + * Registers the new list of functions to the signal name, and returns the + * old SignalHandler. + */ + private static SignalHandler setHandler(String signame, Seqable fns) { + Signal sig = new Signal(signame); + SignalFolder folder = new SignalFolder(fns); + SignalHandler oldHandler = Signal.handle(sig, folder); + return oldHandler; + } + + /** + * Registers the signal name to a List of Runnables, where each callable + * returns an Object. The signal handling is performed as follows: The first + * callable is called, and if it returns a value equal to <code>false</code> + * or <code>null</code> it will stop. Otherwise it will repeat on the next + * callable, until there are no more left. + * + * @param signame the signal name to register this list of callables on. + * @param fns the list of Runnables to (potentially) call. + */ + static synchronized void register(String signame, Seqable fns) { + SignalHandler old = setHandler(signame, fns); + if (!originalHandlers.containsKey(signame)) { + originalHandlers.put(signame, old); + } + } + + /** + * Resets/reinits the signal to be handled by its original signal handler. + * + * @param signame the name of the signal to reinit. + */ + static synchronized void resetDefaultHandler(String signame) + throws SignalHandlerNotFoundException { + if (originalHandlers.containsKey(signame)) { + SignalHandler original = originalHandlers.get(signame); + Signal sig = new Signal(signame); + Signal.handle(sig, original); + originalHandlers.remove(sig); + SignalAtoms.getSignalAtom(signame).reset(getHandlerSeq(signame)); + // As the Atom has a watch which calls register, the handle has been + // modified again. Perform another handle call to fix this: + Signal.handle(sig, original); + } + } + + /** + * Resets/reinits all the signals back to their original signal handlers, + * discarding all possible changes done to them. + */ + static synchronized void resetAll() throws SignalHandlerNotFoundException { + // To get around the fact that we cannot remove elements from a set + // while iterating over it. + List<String> signames = new ArrayList<String>(originalHandlers.keySet()); + for (String signame : signames) { + resetDefaultHandler(signame); + } + } + + /** + * Returns a set of Runnables which is used within the SignalFolder + * handling the Signal, or a PersistentSet with a Runnable SignalHandler if + * the SignalHandler is not a SignalFolder. + * + * @param signame The name of the Signal. + * + * @return A list with the Runnables used in the SignalFolder. + */ + static synchronized Seqable getHandlerSeq(String signame) { + Signal sig = new Signal(signame); + // Urgh, no easy way to get current signal handler. + // Double-handle to get current one without issues. + SignalHandler current = Signal.handle(sig, SignalHandler.SIG_DFL); + Signal.handle(sig, current); + if (current instanceof SignalFolder) { + return ((SignalFolder)current).originalList; + } + else { + Runnable wrappedHandler = new RunnableSignalHandler(sig, current); + return PersistentHashSet.create(wrappedHandler); + } + } + + /** + * A Runnable SignalHandler is simply a Runnable which wraps a + * SignalHandler. This is used internally to ensure that people can perform + * <code>swap!</code> in Clojure programs without worrying that the default + * SignalHandler will cause issues as it's not Runnable by default. + */ + private static class RunnableSignalHandler implements Runnable { + private final Signal sig; + private final SignalHandler handler; + + /** + * Returns a Runnable which will call <code>handler.handle(sig)</code> + * whenever called. + */ + RunnableSignalHandler(Signal sig, SignalHandler handler) { + this.sig = sig; + this.handler = handler; + } + + /** + * Calls the SignalHandler with the signal provided at construction, and + * returns true if the handler doesn't cast any exception. If the + * handler cast an exception, false is returned, and if the handler + * casts an error, that error is cast. + * + * @return true if the handler doesn't throw an exception, false + * otherwise. + */ + @Override + public void run() { + handler.handle(sig); + } + } + + static void raise(String signame) { + Signal sig = new Signal(signame); + Signal.raise(sig); + } +} diff --git a/test/beckon/core_test.clj b/test/beckon/core_test.clj new file mode 100644 index 0000000..7ad2ec2 --- /dev/null +++ b/test/beckon/core_test.clj @@ -0,0 +1,7 @@ +(ns beckon.core-test + (:require [clojure.test :refer :all] + [beckon.core :refer :all])) + +(deftest a-test + (testing "FIXME, I fail." + (is (= 0 1)))) -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/beckon-clojure.git _______________________________________________ pkg-java-commits mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-java-commits

