librelist archives

« back to archive

Reloader causing locking errors with ZODB

Reloader causing locking errors with ZODB

From:
Dag Odenhall
Date:
2010-10-19 @ 11:51
Should maybe file a bug report, but I'd like to see some discussion and
see if someone can think of a solution.

Using Flask with ZODB, the reloader causes this error from ZODB:


(stutuz)dag@gumri:~/Dokument/stutuz$ ./manage.py runserver -r
 * Running on http://127.0.0.1:5000/
 * Restarting with reloader...
No handlers could be found for logger "zc.lockfile"
Traceback (most recent call last):
  File "./manage.py", line 22, in <module>
    manager.run()
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/Flask_Script-0.3.1-py2.6.egg/flaskext/script.py",
line 684, in run
    sys.argv[2:])
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/Flask_Script-0.3.1-py2.6.egg/flaskext/script.py",
line 653, in handle
    app = self.create_app(**app_namespace.__dict__)
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/Flask_Script-0.3.1-py2.6.egg/flaskext/script.py",
line 432, in create_app
    return self.app(**kwargs)
  File "/home/dag/Dokument/stutuz/stutuz/__init__.py", line 24, in
create_app
    app.config.from_object(config)
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/Flask-0.6-py2.6.egg/flask/config.py",
line 146, in from_object
    obj = import_string(obj)
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/Werkzeug-0.6.2-py2.6.egg/werkzeug/utils.py",
line 526, in import_string
    return getattr(__import__(module, None, None, [obj]), obj)
  File "/home/dag/Dokument/stutuz/stutuz/configs/development.py", line
22, in <module>
    ZODB_STORAGE = FileStorage('var/db/development.fs')
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/ZODB3-3.10.0-py2.6-linux-i686.egg/ZODB/FileStorage/FileStorage.py",
line 126, in __init__
    self._lock_file = LockFile(file_name + '.lock')
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/zc.lockfile-1.0.0-py2.6.egg/zc/lockfile/__init__.py",
line 76, in __init__
    _lock_file(fp)
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/zc.lockfile-1.0.0-py2.6.egg/zc/lockfile/__init__.py",
line 59, in _lock_file
    raise LockError("Couldn't lock %r" % file.name)
zc.lockfile.LockError: Couldn't lock u'var/db/development.fs.lock'


I know it's related to the reloader because it runs fine without it. The
relevant ZODB code:

    _flags = fcntl.LOCK_EX | fcntl.LOCK_NB

    def _lock_file(file):
        try:
            fcntl.flock(file.fileno(), _flags)
        except IOError:
            raise LockError("Couldn't lock %r" % file.name)

Changing it to reraise the IOError:


(stutuz)dag@gumri:~/Dokument/stutuz$ ./manage.py runserver -r
 * Running on http://127.0.0.1:5000/
 * Restarting with reloader...
No handlers could be found for logger "zc.lockfile"
Traceback (most recent call last):
  File "./manage.py", line 22, in <module>
    manager.run()
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/Flask_Script-0.3.1-py2.6.egg/flaskext/script.py",
line 684, in run
    sys.argv[2:])
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/Flask_Script-0.3.1-py2.6.egg/flaskext/script.py",
line 653, in handle
    app = self.create_app(**app_namespace.__dict__)
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/Flask_Script-0.3.1-py2.6.egg/flaskext/script.py",
line 432, in create_app
    return self.app(**kwargs)
  File "/home/dag/Dokument/stutuz/stutuz/__init__.py", line 24, in
create_app
    app.config.from_object(config)
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/Flask-0.6-py2.6.egg/flask/config.py",
line 146, in from_object
    obj = import_string(obj)
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/Werkzeug-0.6.2-py2.6.egg/werkzeug/utils.py",
line 526, in import_string
    return getattr(__import__(module, None, None, [obj]), obj)
  File "/home/dag/Dokument/stutuz/stutuz/configs/development.py", line
22, in <module>
    ZODB_STORAGE = FileStorage('var/db/development.fs')
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/ZODB3-3.10.0-py2.6-linux-i686.egg/ZODB/FileStorage/FileStorage.py",
line 126, in __init__
    self._lock_file = LockFile(file_name + '.lock')
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/zc.lockfile-1.0.0-py2.6.egg/zc/lockfile/__init__.py",
line 77, in __init__
    _lock_file(fp)
  File

"/home/dag/.virtualenvs/stutuz/lib/python2.6/site-packages/zc.lockfile-1.0.0-py2.6.egg/zc/lockfile/__init__.py",
line 57, in _lock_file
    fcntl.flock(file.fileno(), _flags)
IOError: [Errno 11] Resource temporarily unavailable


As I mentioned, it runs fine without the reloader and also unit tests
for read and write in a route also passes.

I can live without the reloader but it's very handy! I also plan to
release a Flask-ZODB extension eventually so would be nice to solve this
issue. Any ideas?

Dag

Re: [flask] Reloader causing locking errors with ZODB

From:
Simon Sapin
Date:
2010-10-19 @ 12:32
  Le 19/10/2010 20:51, Dag Odenhall a écrit :
> Should maybe file a bug report, but I'd like to see some discussion 
> and see if someone can think of a solution.
>
> Using Flask with ZODB, the reloader causes this error from ZODB:
> [...]

Hi,

The reloader actually spawns a new Python process (and kill it and start 
another one to reload), but your code may already have been imported 
when that happens. This means that anything that gets done at import 
time (such as initializing your Flask app) is done twice. (Once per 
process.)

In your code, this is probably it:

   File "/home/dag/Dokument/stutuz/stutuz/configs/development.py", line 
22, in <module>
     ZODB_STORAGE = FileStorage('var/db/development.fs')

Maybe you can setup ZODB "lazily": not at import time but the first time 
it is used. Werkzeug's cached_property might help:
http://werkzeug.pocoo.org/documentation/0.6.2/utils.html#werkzeug.cached_property

Regards,
-- 
Simon Sapin

Re: [flask] Reloader causing locking errors with ZODB

From:
Armin Ronacher
Date:
2010-10-19 @ 15:25
Hi,

Alternatively create a separate file called "run.py" and run the Flask 
server like this:

def import_on_first_request(environ, start_response):
     from yourapplication import app
     app.debug = True
     return app(environ, start_response)

from werkzeug import run_simple
run_simple('localhost', 5000, import_on_first_request)

And then run this file instead.


Regards,
Armin

Re: [flask] Reloader causing locking errors with ZODB

From:
Dag Odenhall
Date:
2010-10-19 @ 16:02
On Tue, 2010-10-19 at 17:25 +0200, Armin Ronacher wrote:
> Hi,
> 
> Alternatively create a separate file called "run.py" and run the Flask 
> server like this:
> 
> def import_on_first_request(environ, start_response):
>      from yourapplication import app
>      app.debug = True
>      return app(environ, start_response)
> 
> from werkzeug import run_simple
> run_simple('localhost', 5000, import_on_first_request)
> 
> And then run this file instead.
> 
> 
> Regards,
> Armin

Thanks; though I prefer the solution that works "naturally" with
existing development server setups.

Maybe Flask-Script should do this?

Re: [flask] Reloader causing locking errors with ZODB

From:
danjac354@gmail.com
Date:
2010-10-19 @ 16:08
> Thanks; though I prefer the solution that works "naturally" with
> existing development server setups.
>
> Maybe Flask-Script should do this?
>
>
In this case you might be better using an app factory that creates
your app dynamically rather than a "bare" app.

Re: [flask] Reloader causing locking errors with ZODB

From:
Dag Odenhall
Date:
2010-10-19 @ 16:32
On Tue, 2010-10-19 at 17:08 +0100, danjac354@gmail.com wrote:
> > Thanks; though I prefer the solution that works "naturally" with
> > existing development server setups.
> >
> > Maybe Flask-Script should do this?
> >
> >
> In this case you might be better using an app factory that creates
> your app dynamically rather than a "bare" app.

I am using the create_app pattern if that's what you mean? Problem is
(was) that it's loading the configuration directly rather than as Armin
suggests, on the first request. Manager needs to do this before it runs
the server though but maybe it'd work if it'd create a new app for the
server.

def import_on_first_request(environ, start_response):
     app = create_app(**opts)  # Ignoring self.app
     return app(environ, start_response)

Duno if this would still be dysfunctional. I guess I could try by making
a separate manager command.

Re: [flask] Reloader causing locking errors with ZODB

From:
danjac354@gmail.com
Date:
2010-10-19 @ 16:37
Maybe you could do that in a Server command subclass (override
handle() or run()). Not quite sure though.

On 19 October 2010 17:32, Dag Odenhall <dag.odenhall@gmail.com> wrote:
> On Tue, 2010-10-19 at 17:08 +0100, danjac354@gmail.com wrote:
>> > Thanks; though I prefer the solution that works "naturally" with
>> > existing development server setups.
>> >
>> > Maybe Flask-Script should do this?
>> >
>> >
>> In this case you might be better using an app factory that creates
>> your app dynamically rather than a "bare" app.
>
> I am using the create_app pattern if that's what you mean? Problem is
> (was) that it's loading the configuration directly rather than as Armin
> suggests, on the first request. Manager needs to do this before it runs
> the server though but maybe it'd work if it'd create a new app for the
> server.
>
> def import_on_first_request(environ, start_response):
>     app = create_app(**opts)  # Ignoring self.app
>     return app(environ, start_response)
>
> Duno if this would still be dysfunctional. I guess I could try by making
> a separate manager command.
>
>

Re: [flask] Reloader causing locking errors with ZODB

From:
Dag Odenhall
Date:
2010-10-19 @ 13:03
You are absolutely right, thank you very much.

For the curious, I changed the config option to be a callable:

ZODB_STORAGE = lambda: FileStorage('var/db/development.fs')

and made the ZODB a cached_property that calls the config callable:

@cached_property
def db(self):
    return DB(self.app.config['ZODB_STORAGE']())

In the testing config I simply don't call (instantiate) the storage
which doesn't take any arguments, so no need for a lambda:

ZODB_STORAGE = DemoStorage

instead of

ZODB_STORAGE = DemoStorage()

Quite a satisfactory solution!