viniciuschiele / flask-apscheduler

Adds APScheduler support to Flask
Apache License 2.0
1.12k stars 185 forks source link

Running job causes detachedinstanceerror #221

Open bnewman70 opened 1 year ago

bnewman70 commented 1 year ago

Is there any chance that when running a job using the scheduler within an app context that it can cause a previous session to detach?

i am using version 1.12.3 of flask-apscheduler, I only get the following error when running a job that uses the database. If I run a job that just prints to screen, I do not get this error.

sqlalchemy.orm.exc.DetachedInstanceError: Parent instance <Users at 0x2792f1448b0> is not bound to a Session; lazy load operation of attribute 'profile' cannot proceed.

The call for profile is used by flask-login on @login_required decorator. I have tried many scenarios and this only seems to happen when a job runs. I am using the @scheduler.task decorator to start the job and have my code within this block

with scheduler.app.app_context():

So this works fine @scheduler.task("interval",id=ups_tracking,seconds=60,max_instances=1,start_date="2000-01-01 12:19:00") def hello_world(): print("hello world job") this produces error @scheduler.task("interval",id=ups_tracking,seconds=60,max_instances=1,start_date="2000-01-01 12:19:00") def hello_world(): with scheduler.app.app_context(): print("hello world job") not even doing any db queries and this only happens when using scheduler.run_job() method on demand.
christopherpickering commented 1 year ago

hey @bnewman70, so if I understand you correctly, the error is only coming up in jobs that have the @login_required decorator on them as well? Can you share code where the decorator is added?

bnewman70 commented 1 year ago

the jobs don't have the @login_required decorator, the routes do.
Using Flask-login 0.5.0 When I try to access an object under the current__user that's when the error occurs, but only after I call scheduler.run_job and the job runs within the block with scheduler.app.app_context():

If I do a run_job in a test job that does not run within a with scheduler.app.app_context(): block, the object under my current_user is correctly pulled from the database with no error.

Heres some code blocks in order of execution:

  1. Route for a page that lists all jobs and allows user to perform actions, like pause, resume and run now. Note i added a current_user.profile call as a work around to avoiding the error @blueprint.route('/jobs', methods=['GET','POST']) @login_required def jobs(): action = request.args.get('action') jobs = scheduler.get_jobs() print('got jobs',len(jobs)) if action == 'pause': print('pausing job') scheduler.pause_job(request.args.get('jid')) flash('Job paused',category='success') elif action == 'resume': scheduler.resume_job(request.args.get('jid')) flash('Job Resumed',category='success') elif action == 'run_now':

    avoid detached profile only in run now, pull profile before job runs

    print(current_user.profile)
    scheduler.run_job(request.args.get('jid'))
    flash('Job Started',category='success')
  2. example job, triggering the detached instance error. If I remove with scheduer.app.app_context(), the error does not occur @scheduler.task("interval",id=hello_world,seconds=60,max_instances=1,start_date="2000-01-01 12:19:00") def hello_world(): with scheduler.app.app_context(): print("hello world job")

  3. In my template I show the user profile image from the current_user.profile in the header of each page.. this triggers the error. I an guessing I only see it on run_now because this page immediately reloads after run. Other pages may not be refreshed during a job run. {% if current_user.profile %} {% for user in current_user.profile %}

    User-Profile-Image
                                {% endfor %}
                            {% endif %}

    Thanks

christopherpickering commented 1 year ago

It is difficult to follow, maybe if you have code on github somewhere it would be easier.

It sounds as though you have a db session, and after you pass it into a job, the session is no longer accessible outside the job.

Are you using the extensions pattern (similar to this example) to create you db?

You could have something like

from flask_sqlalchemy import SQLAlchemy
from flask_sqlalchemy_caching import CachingQuery

db = SQLAlchemy(query_class=CachingQuery)

in your extensions.py and then import it where you need db connections, and use it inside the app_context().

In your init.py you would still need to also import db and initialize it with db.init_app(app)