progrium / ginkgo Goto Github PK
View Code? Open in Web Editor NEWPython service microframework
Home Page: http://ginkgo.readthedocs.org
License: MIT License
Python service microframework
Home Page: http://ginkgo.readthedocs.org
License: MIT License
And remove bad examples
If one of a service's greenlets calls self.stop(), that greenlet will be killed (as part of shutting down the gevent AsyncManger -- cf. gevent.py's do_stop() method) before self.stop() finishes execution.
This means that the service will never actually reach the "stopped" state, with the result that anything waiting on the service to stop before returning (such as serve_forever()) will wait indefinitely.
Since you should be using a Service's interface to create greenthreads (spawn, spawn_later), we've almost abstracted the core gevent API. However, we still commonly call gevent.sleep() to yield or wait for a period. If we abstract this, we can later use a common interface whether you're using OS threads or greenlets. sleep() shouldn't live on Service because it's more global. However, I'd like to avoid having global functions on, say, the gservice module. So perhaps it makes most sense on Context.
You should be able to specify a module instead of a configuration file such as:
gservice path.to.service.module start
This module will contain a service factory function (service()). Note that -C is not necessary, as it will attempt to use default configuration values. If -C is used, the configuration file will be used for configuration. However, if a service factory is in the configuration file, it will use that factory instead.
The canonical way to define a service factory is in the module that defines the top level service. You should always prefer to use the module lookup unless you need to override the service factory for some reason (however, if configuration is done correctly, you shouldn't have to).
Here is an example service module called package.myservice (in, say, package/myservice.py):
from gservice.core import Service
class MyService(Service):
def do_start(self):
pass
def service():
return MyService()
Now you can run gservice package.myservice start
I have problem installing ginkgo. When I run setup.py following exception occurs.
C:\git\ginkgo>python setup.py install
Traceback (most recent call last):
File "setup.py", line 5, in
from ginkgo import version
File "C:\git\ginkgo\ginkgo__init__.py", line 25, in
from .core import Service
File "C:\git\ginkgo\ginkgo\core.py", line 19, in
from .util import AbstractStateMachine
File "C:\git\ginkgo\ginkgo\util.py", line 7, in
import resource
ImportError: No module named resource
Looking at this, I see two paths:
I'll take a look at going down option 1 later this week in a branch and see if clean code can be made.
Sean
If ProxyTypes isn't already installed, setup.py will fail due to setup.py's "from ginkgo import __version__
":
File "setup.py", line 39, in <module>
from ginkgo import __version__
File "ginkgo/ginkgo/__init__.py", line 4, in <module>
from .config import Config as _Config
File "ginkgo/config.py", line 3, in <module>
from peak.util.proxies import ObjectWrapper
ImportError: No module named peak.util.proxies
We should support a status action that will check the status of the daemon using the pidfile specified (or using the default pidfile).
Inspired by: http://code.google.com/p/supay/
When gservice programs crash they leave a pid file. On startup, they fail to lock the pid file instead of correctly determining there is no process at that pid and removing it.
Sean
When gservice starts up it incorrectly returns a 0 return code for the following error cases:
I've included the traceback for issue #3 below.
Traceback (most recent call last):
File "/usr/local/python/bin/gservice", line 8, in
load_entry_point('gservice==0.2.0', 'console_scripts', 'gservice')()
File "/usr/local/python/lib/python2.7/site-packages/gservice-0.2.0-py2.7.egg/gservice/runner.py", line 22, in main
Runner().do_action()
File "/usr/local/python/lib/python2.7/site-packages/gservice-0.2.0-py2.7.egg/gservice/runner.py", line 255, in do_action
getattr(self, func)(_args, *_kwargs)
File "/usr/local/python/lib/python2.7/site-packages/gservice-0.2.0-py2.7.egg/gservice/runner.py", line 247, in _start
super(Runner, self)._start()
File "/usr/local/python/lib/python2.7/site-packages/python_daemon-1.6-py2.7.egg/daemon/runner.py", line 124, in _start
self.daemon_context.open()
File "/usr/local/python/lib/python2.7/site-packages/python_daemon-1.6-py2.7.egg/daemon/daemon.py", line 346, in open
self.pidfile.enter()
File "build/bdist.linux-i686/egg/lockfile/init.py", line 226, in enter
File "/usr/local/python/lib/python2.7/site-packages/python_daemon-1.6-py2.7.egg/daemon/pidfile.py", line 42, in acquire
super(TimeoutPIDLockFile, self).acquire(timeout, _args, *_kwargs)
File "build/bdist.linux-i686/egg/lockfile/pidlockfile.py", line 85, in acquire
lockfile.LockTimeout
Just a newer, better way to do option/argument parsing.
Gservice has no license at the moment, which keeps me from using it professionally. Are there plans to a a license?
It's common practice to use SIGHUP for reloading configuration, but we're using SIGUSR1. We should leave user signals up to the service application logic.
Ref: http://theory.uwinnipeg.ca/CPAN/perl/pod/perlipc/handling_the_sighup_signal_in_daemons.html
When starting a broken service with ginkgoctl, everything looks hunky dory:
# ginkgoctl ../collector.conf.py start || echo fail
Starting process with ../collector.conf.py...
But in fact, an exception had occurred somewhere inside the user's service:
# ginkgo ../collector.conf.py
Starting process with ../collector.conf.py...
Traceback (most recent call last):
File "/usr/local/python/bin/ginkgo", line 9, in <module>
load_entry_point('Ginkgo==0.5.0dev', 'console_scripts', 'ginkgo')()
File "/usr/local/python-2.7/lib/python2.7/site-packages/Ginkgo-0.5.0dev-py2.7.egg/ginkgo/runner.py", line 44, in run_ginkgo
ControlInterface().start(args.target)
File "/usr/local/python-2.7/lib/python2.7/site-packages/Ginkgo-0.5.0dev-py2.7.egg/ginkgo/runner.py", line 142, in start
app.serve_forever()
File "/usr/local/python-2.7/lib/python2.7/site-packages/Ginkgo-0.5.0dev-py2.7.egg/ginkgo/core.py", line 166, in serve_forever
self.start()
File "/usr/local/python-2.7/lib/python2.7/site-packages/Ginkgo-0.5.0dev-py2.7.egg/ginkgo/core.py", line 107, in start
child.start(block_until_ready)
File "/usr/local/python-2.7/lib/python2.7/site-packages/Ginkgo-0.5.0dev-py2.7.egg/ginkgo/core.py", line 107, in start
child.start(block_until_ready)
File "/usr/local/python-2.7/lib/python2.7/site-packages/Ginkgo-0.5.0dev-py2.7.egg/ginkgo/core.py", line 107, in start
child.start(block_until_ready)
File "/usr/local/python-2.7/lib/python2.7/site-packages/Ginkgo-0.5.0dev-py2.7.egg/ginkgo/core.py", line 109, in start
ready = not self.do_start()
...
and on down into the user's code.
This is a problem, since it's not possible for init.d scripts to detect whether or not they successfully started a ginkgo service.
Leaving a file in the current directory is annoying and messy. It's usually never checked. If you want to see the output of running the daemon, you run it without daemonizing. If you want to daemonize and see the logs, you can explicitly define where to put them. Otherwise, we should just not care to log.
I've noticed several times that when you start a service with ginkoctl, it won't shut down or restart unless you send it SIGKILL.
This seems to have been particularly the case with one of my ginkgo services, which simply wraps flask using ginkgo's wrapper.
In one case, we were able to observe two ginkgoctl processes running for the same conf file -- one was the original start process, and the other was a restart process that we had kicked off to restart the service.
Right now we don't let you because ... well, why would you? The value is managed by configuration. However, it makes sense for easy dependency injection to just set it directly instead of going through configuration. It seems like that's part of the advantage of having a class variable there anyway...
Similar to Process
Please put Ginkgo on PyPI to make it easier to deploy!
Currently there is no way to wait() on ready state because it's assumed the only time you have to wait for this is on start. This is why start has the default option to block_until_ready. However, it's likely a service will become unavailable (for example, a client loses its connection), so it makes sense it should drop out of ready state. Code that uses this service should then be able to block until it becomes ready again.
The simplest solution is to add a method to Service such as wait() or wait_until_ready(). However, changes to the Service interface should be done carefully.
It would be great for gingkoctl to provide a command that asked a service if it was ready.
The reasoning behind this is for startup scripts that use ginkgo start
. Since the process backgrounds itself, it will return whether or not the service came up cleanly. The command should either return when it gets a response from the service, timeout, or notice that the service's pid is no longer running.
In Service.start() and .serve_forever() we catch all exceptions and call stop(). However, we added traceback.print_exc() so that if stop() raises, we can see the original exception. It seems like there is a better way to do this. Perhaps an inner try/except? Even though tests pass, we still get nasty, confusing output:
progrium-twilio:gservice progrium$ python setup.py test
running test
............................Traceback (most recent call last):
File "/Users/progrium/Projects/gservice/gservice/core.py", line 179, in start
ready = self.do_start()
File "/Users/progrium/Projects/gservice/gservice/tests/test_service.py", line 49, in do_start
raise Exception("Error")
Exception: Error
.Traceback (most recent call last):
File "/Users/progrium/Projects/gservice/gservice/core.py", line 181, in start
self._ready_event.wait(self.ready_timeout)
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/gevent-0.13.3-py2.7-macosx-10.6-x86_64.egg/gevent/event.py", line 74, in wait
result = get_hub().switch()
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/gevent-0.13.3-py2.7-macosx-10.6-x86_64.egg/gevent/hub.py", line 163, in switch
return greenlet.switch(self)
NotImplementedError: Second Error
...............
----------------------------------------------------------------------
Ran 44 tests in 7.403s
OK
When starting gservice, if the pidfile lock fails gservice still returns 0. This should be an error return.
Traceback (most recent call last):
File "/usr/local/python/bin/gservice", line 8, in
load_entry_point('gservice==0.2.0', 'console_scripts', 'gservice')()
File "/usr/local/python/lib/python2.7/site-packages/gservice-0.2.0-py2.7.egg/gservice/runner.py", line 22, in main
Runner().do_action()
File "/usr/local/python/lib/python2.7/site-packages/gservice-0.2.0-py2.7.egg/gservice/runner.py", line 255, in do_action
getattr(self, func)(_args, *_kwargs)
File "/usr/local/python/lib/python2.7/site-packages/gservice-0.2.0-py2.7.egg/gservice/runner.py", line 247, in _start
super(Runner, self)._start()
File "/usr/local/python/lib/python2.7/site-packages/python_daemon-1.6-py2.7.egg/daemon/runner.py", line 124, in _start
self.daemon_context.open()
File "/usr/local/python/lib/python2.7/site-packages/python_daemon-1.6-py2.7.egg/daemon/daemon.py", line 346, in open
self.pidfile.enter()
File "build/bdist.linux-i686/egg/lockfile/init.py", line 226, in enter
File "/usr/local/python/lib/python2.7/site-packages/python_daemon-1.6-py2.7.egg/daemon/pidfile.py", line 42, in acquire
super(TimeoutPIDLockFile, self).acquire(timeout, _args, *_kwargs)
File "build/bdist.linux-i686/egg/lockfile/pidlockfile.py", line 85, in acquire
lockfile.LockTimeout
runpy was made to make python -m <module>
work. It is probably a better way to load a module and get its globals. It's also useful for looking up module service factories.
As well its current interface, Group should be able to be used as a read-only dict. This way you can pass a config group into another framework as a dict quite easily:
logfile = "/var/log/foo.log"
class flask:
debug = False
testing = True
secret_key = "woifh28fhw93fh"
Then in your app, setting up flask:
app.config.update(settings.group('flask'))
import random
from ginkgo.core import Service
from ginkgo.runner import ControlInterface
class SomeService(Service):
def do_start(self):
print random.randint(0,1)
ginkgo test_managed_app2.conf.py [ruby-1.9.2-p320]
Starting process with test_managed_app2.conf.py...
2012-07-27 15:15:44,946 INFO zmq_services: Connecting to REP socket with address tcp://0.0.0.0:1112
2012-07-27 15:15:44,946 INFO zmq_services: Binding to REP socket with address tcp://0.0.0.0:2632
Traceback (most recent call last):
File "/Users/darvin/Projects/ss9/Python/Heyghoge/pyenv/lib/python2.7/site-packages/gevent/greenlet.py", line 390, in run
result = self._run(*self.args, **self.kwargs)
File "/Users/darvin/Projects/ss9/Python/Heyghoge/src/base_application.py", line 223, in _ticking
self.handle_tick()
File "/Users/darvin/Projects/ss9/Python/Heyghoge/src/test_managed_app2.conf.py", line 10, in handle_tick
self.health.load += random.randint(0,10)
AttributeError: 'NoneType' object has no attribute 'randint'
<Greenlet at 0x10fff4550: <bound method TestManagedApp2._ticking of <<run_path>.TestManagedApp2 object at 0x10fffba90>>> failed with AttributeError
Hi all,
I am looking to upload my project 'ginkgo' to Pypi. I found this package is already there and occupy the name.
However, I find this package hasn't been updated for 11 years and perhaps been deprecated.
Would you mind transferring the name 'ginkgo' to me?
Thanks, and please let me know if you have concerns.
Kang
Context should be the top level gservice object that a Runner will create to start an application. It should be responsible for parsing a configuration file if provided, or taking/overriding with a configuration dictionary (although the code should live in the config module). This means it should also run the service factory in the configuration.
It should also own the named global services. The _main_services in Service should live in Context. Services should have a reference to the Context (all services live within a context).
ultimately, you should be able to instantiate a gservice application by creating a Context instance. For example:
app1 = Context(file='/path/to/app1.conf.py')
app2 = Context(file='/path/to/app2.conf.py')
Allowing you to write functional tests of interacting applications in the same process.
We use asserts in various places, but we should reserve asserts for conditions a user should never hit. Most of our asserts are conditions the user can easily run into. Then we might also want to place asserts to actually assert assumptions about state. If a user ever reports seeing these, we will know there is a bug.
It should be safe to assume what you do in Service.init will not be affected by daemonizing. Opening files or creating ZMQ contexts should be safe. RIght now, this happens before daemonization. The application service needs to initialized after daemonizing.
Putting the pidfile in the current directory by default feels messy. It should be in a global location by default (so you can run start/stop from anywhere). Inspired by this:
http://stackoverflow.com/questions/3957242/storing-pid-file-for-a-daemon-run-as-user
We can name the pidfile something more appropriate (myservice.conf.py -> myservice.pid or path.to.service.module -> path.to.service.module.pid) and then prepend with . and put in the current home directory. ie:
~/.myservice.pid
~/.path.to.service.module.pid
I've gone back and forth about this. Imagine a service with these states:
The require_ready
decorator would wait for the ready state (although it could already do this). There could be a wait_for(state)
method. Services could go out of ready state.
This is a more general solution to the issue here: https://github.com/progrium/gservice/issues/21
Nose does something to the environment where it has trouble loading configuration. Shreve has more details...
Right now, config loads into a global registry. But it should namespace these configurations.
Also, there is a Namespace class used by load() now that is used for making sub configuration namespaces... it has nothing to do with this issue. Perhaps that should be renamed. Or maybe this should be called something else.
Reload currently gets caught by the runner, reloads logging configuration then calls reload on the top level service (RootService). When RootService becomes Context and Context is responsible for configuration, then it would be nice for Context to reload configuration.
If we can wait() on ready state, then we can make require_ready block within a timeout until ready. This makes it more useful in that you can continue to call that method without handling an exception, assuming the service will soon return to a ready state.
setproctitle is cool but is not a necessary dependency. Let's use it if we have it, but otherwise just skip it. However, it's useful to have a name for a service instance. For example, what we name the pidfile. So we should have a good default name value that can be overwritten by the name option if present. And if setproctitle is present, we'll use this name to set the proc title.
The default name will be the module path of the service returned by the service factory: mypackage.myservice
If a configuration file is used, we'll use the filename prefix as the name suffix: dev.conf.py -> mypackage.myservice-dev
However, it's likely we won't know the service module path until we actually run because of the lazy eval nature of the service factory. In that case, we'll use the module path provided by the service lookup (which should be close if not that right module path for the service). Ie:
gservice mypackage.myservice start
Would mean we use "mypackage.myservice" despite what the service factory returns in that module.
Alternatively, if just a configuration file is used, we'll name it after the configuration file:
gservice -C /path/to/myservice.conf.py start
Would mean we use "myservice".
During a recent code review, I mentioned that Ginkgo's current configuration file standard, where configuration files end in .conf.py, causes nose tests to break. Here's how you may reproduce it:
$ nosetests
----------------------------------------------------------------------
Ran 0 tests in 0.009s
OK
$ touch ginkgo/test.conf.py
$ nosetests
E
======================================================================
ERROR: Failure: ImportError (No module named ginkgo)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/hblanks/virtualenv/main/lib/python2.7/site-packages/nose/loader.py", line 390, in loadTestsFromName
addr.filename, addr.module)
File "/Users/hblanks/virtualenv/main/lib/python2.7/site-packages/nose/importer.py", line 39, in importFromPath
return self.importFromDir(dir_path, fqname)
File "/Users/hblanks/virtualenv/main/lib/python2.7/site-packages/nose/importer.py", line 71, in importFromDir
fh, filename, desc = find_module(part, path)
ImportError: No module named ginkgo
----------------------------------------------------------------------
Ran 1 test in 0.010s
FAILED (errors=1)
$
This example uses the current ginkgo source tree and the latest version of nose. It's worth noting that any source tree should cause this behavior, provided you've put a *.conf.py file inside a directory that nose is going to search.
(My unsolicited two cents here is that Ginkgo files would be better either just as .py files, or else as INI-formatted .conf files, akin to CherryPy's.)
The link on the README should link to:
http://pyvideo.org/video/642/throwing-together-distributed-services-with-geven
not
http://pyvideo.org/video/637/throwing-together-distributed-services-with-geven
which is actually a different talk
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.