Repository: fossasia/open-event-server

Issue:
Only the first task works on Celery
**Describe the bug**
The task to export the event as a zip fails.

**To Reproduce**
Steps to reproduce the behavior:
1. Use `/v1/events/{event_identifier}/export/json` API to start the export
(Make sure Celery is working)
1. You will get the stack trace in the terminal at this point in time
1. Use `/v1/tasks/{task_id}` to check the result of the task. It will return "FAILURE"

**Expected behavior**
Zipping should work as stated.

**Stacktrace**
```sh
[2018-05-24 17:31:42,874: ERROR/MainProcess] Task export.event[d23f127a-2d47-4958-a9c2-214fa53560e7] raised unexpected: OperationalError('(psycopg2.OperationalError) SSL SYSCALL error: EOF detected\n',)
Traceback (most recent call last):
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/celery/app/trace.py", line 240, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/home/mayank/OpenSource/open-event-server/app/api/helpers/request_context_task.py", line 46, in __call__
    result = call()
  File "/home/mayank/OpenSource/open-event-server/app/api/helpers/request_context_task.py", line 32, in <lambda>
    call = lambda: super(RequestContextTask, self).__call__(*args, **kwargs)
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/celery/app/trace.py", line 438, in __protected_call__
    return self.run(*args, **kwargs)
  File "/home/mayank/OpenSource/open-event-server/app/api/helpers/tasks.py", line 60, in export_event_task
    event = safe_query(db, Event, 'id', event_id, 'event_id')
  File "/home/mayank/OpenSource/open-event-server/app/api/helpers/db.py", line 46, in safe_query
    record = self.session.query(model).filter(getattr(model, column_name) == value).one()
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2814, in one
    ret = self.one_or_none()
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2784, in one_or_none
    ret = list(self)
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2855, in __iter__
    return self._execute_and_instances(context)
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2878, in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 945, in execute
    return meth(self, multiparams, params)
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/sql/elements.py", line 263, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1053, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1189, in _execute_context
    context)
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1402, in _handle_dbapi_exception
    exc_info
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 203, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 186, in reraise
    raise value.with_traceback(tb)
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1182, in _execute_context
    context)
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 470, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) SSL SYSCALL error: EOF detected
 [SQL: 'SELECT events.id AS events_id, events.identifier AS events_identifier, events.name AS events_name, events.external_event_url AS events_external_event_url, events.logo_url AS events_logo_url, events.starts_at AS events_starts_at, events.ends_at AS events_ends_at, events.timezone AS events_timezone, events.latitude AS events_latitude, events.longitude AS events_longitude, events.location_name AS events_location_name, events.searchable_location_name AS events_searchable_location_name, events.description AS events_description, events.original_image_url AS events_original_image_url, events.thumbnail_image_url AS events_thumbnail_image_url, events.large_image_url AS events_large_image_url, events.icon_image_url AS events_icon_image_url, events.organizer_name AS events_organizer_name, events.is_map_shown AS events_is_map_shown, events.has_organizer_info AS events_has_organizer_info, events.organizer_description AS events_organizer_description, events.is_sessions_speakers_enabled AS events_is_sessions_speakers_enabled, events.privacy AS events_privacy, events.state AS events_state, events.event_type_id AS events_event_type_id, events.event_topic_id AS events_event_topic_id, events.event_sub_topic_id AS events_event_sub_topic_id, events.ticket_url AS events_ticket_url, events.code_of_conduct AS events_code_of_conduct, events.schedule_published_on AS events_schedule_published_on, events.is_ticketing_enabled AS events_is_ticketing_enabled, events.deleted_at AS events_deleted_at, events.payment_country AS events_payment_country, events.payment_currency AS events_payment_currency, events.paypal_email AS events_paypal_email, events.is_tax_enabled AS events_is_tax_enabled, events.can_pay_by_paypal AS events_can_pay_by_paypal, events.can_pay_by_stripe AS events_can_pay_by_stripe, events.can_pay_by_cheque AS events_can_pay_by_cheque, events.can_pay_by_bank AS events_can_pay_by_bank, events.can_pay_onsite AS events_can_pay_onsite, events.cheque_details AS events_cheque_details, events.bank_details AS events_bank_details, events.onsite_details AS events_onsite_details, events.created_at AS events_created_at, events.pentabarf_url AS events_pentabarf_url, events.ical_url AS events_ical_url, events.xcal_url AS events_xcal_url, events.is_sponsors_enabled AS events_is_sponsors_enabled, events.discount_code_id AS events_discount_code_id \nFROM events \nWHERE events.id = %(id_1)s'] [parameters: {'id_1': '2'}]

```
**Additional details (please complete the following information):**
 - OS: Ubuntu
 - Python Version 3.6.4
 - `HEAD` Commit hash [e.g. `4629c62`]



Discussion:
For me GET `/v1/tasks/{task_id}` gives
```
"state": "WAITING"
```
For me, celery gives this error for the task `GET` request:
```
  File "/open-event-server/app/api/helpers/tasks.py", line 64, in export_event_task
    path = event_export_task_base(event_id, settings)
  File "/open-event-server/app/api/exports.py", line 69, in event_export_task_base
    path = export_event_json(event_id, settings)
  File "/open-event-server/app/api/helpers/export_helpers.py", line 209, in export_event_json
    fp.write(data_str)
TypeError: write() argument must be str, not bytes
```
@bhaveshAn Make sure celery is working
@schedutron That's weird. So there is some problem definitely. I will look into it
@schedutron Must be for python 3.5

Needs to be fixed though. 
@iamareebjamal @mayank8318 Perhaps a simple call to `data_str.decode('utf-8')` may fix the error I get.
No, there are two errors here, the error for this issue is not related to byte str fiasco
@schedutron Are you using the right Python version?
Python version is 3+ so it needs to be fixed @mayank8318 This doesn't happen in 3.6 but doesn't mean it shouldn't be fixed for 3.5
@iamareebjamal Should I open another issue for this or do it in this one itself? Also, what would be the best way to handle this?
As we can't gather all methods which have byte str problems due to lack of types in Python, we'll have to fix this as errors pop up

And yes, create a new issue. Change bytes to str in all instances of `file.write`
@iamareebjamal Okay :)
After #4785 the following happens :
## Python 3.5
- Task to zip runs once successfully
- But if you repeat the task the following stack trace is obtained
```sh
[2018-05-26 12:05:52,976: ERROR/MainProcess] Task export.event[7cd91235-c169-4a0a-84be-6b4b58d09e00] raised unexpected: StatementError("(sqlalchemy.exc.InvalidRequestError) Can't reconnect until invalid transaction is rolled back",)
Traceback (most recent call last):
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/celery/app/trace.py", line 240, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/home/mayank/OpenSource/open-event-server/app/api/helpers/request_context_task.py", line 39, in __call__
    return call()
  File "/home/mayank/OpenSource/open-event-server/app/api/helpers/request_context_task.py", line 32, in <lambda>
    call = lambda: super(RequestContextTask, self).__call__(*args, **kwargs)
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/celery/app/trace.py", line 438, in __protected_call__
    return self.run(*args, **kwargs)
  File "/home/mayank/OpenSource/open-event-server/app/api/helpers/tasks.py", line 60, in export_event_task
    event = safe_query(db, Event, 'id', event_id, 'event_id')
  File "/home/mayank/OpenSource/open-event-server/app/api/helpers/db.py", line 46, in safe_query
    record = self.session.query(model).filter(getattr(model, column_name) == value).one()
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/orm/query.py", line 2814, in one
    ret = self.one_or_none()
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/orm/query.py", line 2784, in one_or_none
    ret = list(self)
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/orm/query.py", line 2855, in __iter__
    return self._execute_and_instances(context)
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/orm/query.py", line 2878, in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 945, in execute
    return meth(self, multiparams, params)
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/sql/elements.py", line 263, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1053, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1121, in _execute_context
    None, None)
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1402, in _handle_dbapi_exception
    exc_info
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/util/compat.py", line 203, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/util/compat.py", line 186, in reraise
    raise value.with_traceback(tb)
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1114, in _execute_context
    conn = self._revalidate_connection()
  File "/home/mayank/anaconda3/envs/openevent3.5/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 424, in _revalidate_connection
    "Can't reconnect until invalid "
sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError) Can't reconnect until invalid transaction is rolled back [SQL: 'SELECT events.id AS events_id, events.identifier AS events_identifier, events.name AS events_name, events.external_event_url AS events_external_event_url, events.logo_url AS events_logo_url, events.starts_at AS events_starts_at, events.ends_at AS events_ends_at, events.timezone AS events_timezone, events.latitude AS events_latitude, events.longitude AS events_longitude, events.location_name AS events_location_name, events.searchable_location_name AS events_searchable_location_name, events.description AS events_description, events.original_image_url AS events_original_image_url, events.thumbnail_image_url AS events_thumbnail_image_url, events.large_image_url AS events_large_image_url, events.icon_image_url AS events_icon_image_url, events.organizer_name AS events_organizer_name, events.is_map_shown AS events_is_map_shown, events.has_organizer_info AS events_has_organizer_info, events.organizer_description AS events_organizer_description, events.is_sessions_speakers_enabled AS events_is_sessions_speakers_enabled, events.privacy AS events_privacy, events.state AS events_state, events.event_type_id AS events_event_type_id, events.event_topic_id AS events_event_topic_id, events.event_sub_topic_id AS events_event_sub_topic_id, events.ticket_url AS events_ticket_url, events.code_of_conduct AS events_code_of_conduct, events.schedule_published_on AS events_schedule_published_on, events.is_ticketing_enabled AS events_is_ticketing_enabled, events.deleted_at AS events_deleted_at, events.payment_country AS events_payment_country, events.payment_currency AS events_payment_currency, events.paypal_email AS events_paypal_email, events.is_tax_enabled AS events_is_tax_enabled, events.can_pay_by_paypal AS events_can_pay_by_paypal, events.can_pay_by_stripe AS events_can_pay_by_stripe, events.can_pay_by_cheque AS events_can_pay_by_cheque, events.can_pay_by_bank AS events_can_pay_by_bank, events.can_pay_onsite AS events_can_pay_onsite, events.cheque_details AS events_cheque_details, events.bank_details AS events_bank_details, events.onsite_details AS events_onsite_details, events.created_at AS events_created_at, events.pentabarf_url AS events_pentabarf_url, events.ical_url AS events_ical_url, events.xcal_url AS events_xcal_url, events.is_sponsors_enabled AS events_is_sponsors_enabled, events.discount_code_id AS events_discount_code_id \nFROM events \nWHERE events.id = %(id_1)s'] [parameters: [{}]]
```
## Python 3.6
- Same as mentioned in the issue
If you export a different event after successfully exporting the first event, do you get the same error?
@schedutron Yes same error
@iamareebjamal I tried solutions from https://github.com/celery/celery/issues/3238, https://github.com/celery/celery/issues/1564, https://github.com/celery/celery/issues/634 but nothing worked. Am I missing any implementation details?
I think this has to do with detached session https://stackoverflow.com/questions/31121948/flask-sqlalchemy-cant-reconnect-until-invalid-transaction-is-rolled-back

https://stackoverflow.com/questions/45607541/flask-sqlalchemy-error-invalid-transaction
@iamareebjamal I am getting `OperationalError: (psycopg2.OperationalError) SSL error: decryption failed or bad record mac` consistently 
**Real Problem**
While trying to solve this issue I realized that the reason this was happening was that second task onwards the old database connections which were used in the first task were being reused thus causing `OperationalError: (psycopg2.OperationalError) SSL error: decryption failed or bad record mac`.
This happens for any Celery task which uses the database.

Reference https://github.com/celery/celery/issues/3238
@srv-twry @bhaveshAn @pradeepgangwar @Kreijstal  this should be a high priority issue since all our tasks use celery and currently, you need to restart celery after every task. I tried a few solutions but none worked. Let's solve it together :smile: 
UPDATE - Tried https://stackoverflow.com/questions/37801766/correctly-managing-postgresql-connections-in-celery-task-for-flask-sqlalchemy-an too.
@mayank8318 Can you try this one: https://stackoverflow.com/questions/25360136/flask-with-create-app-sqlalchemy-and-celery?rq=1
@srv-twry Nope - 
```sh
[2018-06-14 16:01:47,397: ERROR/MainProcess] Task export.event[ec6e075e-e576-4389-a20f-8f8eadf50719] raised unexpected: AttributeError("'Celery' object has no attribute 'app'",)
Traceback (most recent call last):
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/celery/app/trace.py", line 240, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/home/mayank/OpenSource/open-event-server/app/api/helpers/request_context_task.py", line 46, in __call__
    result = call()
  File "/home/mayank/OpenSource/open-event-server/app/api/helpers/request_context_task.py", line 32, in <lambda>
    call = lambda: super(RequestContextTask, self).__call__(*args, **kwargs)
  File "/home/mayank/anaconda3/envs/openevent3/lib/python3.6/site-packages/celery/app/trace.py", line 438, in __protected_call__
    return self.run(*args, **kwargs)
  File "/home/mayank/OpenSource/open-event-server/app/api/helpers/tasks.py", line 70, in export_event_task
    with celery.app.app_context():
AttributeError: 'Celery' object has no attribute 'app'

```
I think we need scoped db sessions, something like this -> http://www.prschmid.com/2013/04/using-sqlalchemy-with-celery-tasks.html

Resolving patch:
diff --git a/app/__init__.py b/app/__init__.py
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -66,9 +66,13 @@ def __call__(self, environ, start_response):
 
 app.wsgi_app = ReverseProxied(app.wsgi_app)
 
+app_created = False
+
 
 def create_app():
-    BlueprintsManager.register(app)
+    global app_created
+    if not app_created:
+        BlueprintsManager.register(app)
     Migrate(app, db)
 
     app.config.from_object(env('APP_CONFIG', default='config.ProductionConfig'))
@@ -134,7 +138,7 @@ def create_app():
                          view_func=app.send_static_file)
 
     # sentry
-    if 'SENTRY_DSN' in app.config:
+    if not app_created and 'SENTRY_DSN' in app.config:
         sentry.init_app(app, dsn=app.config['SENTRY_DSN'])
 
     # redis
@@ -149,6 +153,7 @@ def create_app():
             except Exception:
                 pass
 
+    app_created = True
     return app, _manager, db, _jwt
 
 
@@ -162,7 +167,8 @@ def track_user():
         current_user.update_lat()
 
 
-def make_celery(app):
+def make_celery(app=None):
+    app = app or create_app()[0]
     celery.conf.update(app.config)
     task_base = celery.Task
 
@@ -180,8 +186,6 @@ def __call__(self, *args, **kwargs):
     return celery
 
 
-celery = make_celery(current_app)
-
 # Health-check
 health = HealthCheck(current_app, "/health-check")
 envdump = EnvironmentDump(current_app, "/environment", include_config=False)
diff --git a/app/api/helpers/tasks.py b/app/api/helpers/tasks.py
--- a/app/api/helpers/tasks.py
+++ b/app/api/helpers/tasks.py
@@ -1,10 +1,11 @@
+import os
+
 import requests
+from flask import current_app
 from marrow.mailer import Mailer, Message
 
+from app import make_celery
 from app.api.helpers.utilities import strip_tags
-from app.views.celery_ import celery
-from flask import current_app
-import os
 
 """
 Define all API v2 celery tasks here
@@ -29,6 +30,8 @@
 from app.api.helpers.storage import UploadedFile, upload, UPLOAD_PATHS
 from app.api.helpers.db import save_to_db
 
+celery = make_celery()
+
 
 @celery.task(name='send.email.post')
 def send_email_task(payload, headers):


Test patch: