2

I've got a simple pyramid app up and running, most of the views are a fairly thin wrapper around an sqlite database, with forms thrown in to edit/add some information.

A couple of times a month a new chunk of data will need to be added to this system (by csv import). The data is saved in an SQL table (the whole process right till commit takes about 4 seconds).

Every time a new chunk of data is uploaded, this triggers a recalculation of other tables in the database. The recalculation process takes a fairly long time (about 21-50 seconds for a month's worth of data).

Currently I just let the browser/client sit there waiting for the process to finish, but I do foresee the calculation process taking more and more time as the system gets more usage. From a UI perspective, this obviously looks like a hung process.

What can I do to indicate to the user that:-

  1. That the long wait is normal/expected?

  2. How MUCH longer they should have to wait (progress bar etc.)?

Note: I'm not asking about long-polling or websockets here, as this isn't really an interactive application and based on my basic knowledge websockets/async are overkill for my purposes.

I guess a follow-on question at this point, am I doing the wrong thing running processes in my view functions? Hardly seem to see that being done in examples/tutorials around the web. Am I supposed to be using celery or similar in this situation?

Ng Oon-Ee
  • 1,193
  • 1
  • 10
  • 26

2 Answers2

1

You're right, doing long calculations in a view function is generally frowned upon - I mean, if it's a typical website with random visitors who are able to hung a webserver thread for a minute then it's a recipe for a DoS vulnerability. But in some situations (internal website, few users, only admin has access to the "upload csv" form) you may get away with it. In fact, I used to have maintenance scripts which ran for hours :)

The trick here is to avoid browser timeouts - at the moment your client sends the data to the server and just sits there waiting for any reply, without any idea whether their request is being processed or not. Generally, at about 60 seconds the browser (or proxy, or frontend webserver) may become impatient and close the connection. Your server process will then get an error trying writing anything to the already closed connection and crash/raise an error.

To prevent this from happening the server needs to write something to the connection periodically, so the client sees that the server is alive and won't close the connection.

"Normal" Pyramid templates are buffered - i.e. the output is not sent to the client until the whole template to generated. Because of that you need to directly use response.app_iter / response.body_file and output some data there periodically.

As an example, you can duplicate the Todo List Application in One File example from Pyramid Cookbook and replace the new_view function with the following code (which itself has been borrowed from this question):

@view_config(route_name='new', request_method='GET', renderer='new.mako')
def new_view(request):
    return {}


@view_config(route_name='new', request_method='POST')
def iter_test(request):
    import time

    if request.POST.get('name'):
        request.db.execute(
            'insert into tasks (name, closed) values (?, ?)',
            [request.POST['name'], 0])
        request.db.commit()


    def test_iter():
        i = 0
        while True:
            i += 1
            if i == 5:
                yield str('<p>Done! <a href="/">Click here</a> to see the results</p>')
                raise StopIteration
            yield str('<p>working %s...</p>' % i)
            print time.time()
            time.sleep(1)

    return Response(app_iter=test_iter())

(of cource, this solution is not too fancy UI-wise, but you said you didn't want to mess with websockets and celery)

Community
  • 1
  • 1
Sergey
  • 11,892
  • 2
  • 41
  • 52
  • I was interested in this question and tried your answer. The output is rendered all at once(all 5 lines). Is that correct? I thought it would display the "working" output one line at a time until finished. – fat fantasma Jun 14 '16 at 23:56
  • @fatfantasma: it may be caused by the webserver (waitress and/or the front webserver, such as Nginx) also buffering the output - see this answer: http://stackoverflow.com/a/21540659/320021 I've tested it with an older setup of Pyramid which uses PasteDeploy and it was printing a line each second. – Sergey Jun 15 '16 at 00:02
  • @fatfantasma: as a fun experiment, you may try writing a lot of whitespace to force the webserver to flush the buffer after each line. The browser output would look the same because the whitespace would be collapsed. Not sure I would try this in production, but something like this would work: yield str('

    working %s...

    %s' % (i, ' ' * 20000))
    – Sergey Jun 15 '16 at 00:06
  • I tried your flushing string but that did not work either. Everything was rendered at the end. I'm am trying to review the waitress docs now to try to configure send_bytes = 1 – fat fantasma Jun 15 '16 at 00:10
  • I got waitress configured with send_bytes =1. The output is now rendered 1 line at a time. I'm glad I got the experiment to work but as that SO question suggests it slows everything down so it's not really worth it. I did learn something today. Thanks :) – fat fantasma Jun 15 '16 at 00:15
  • @fatfantasma: did "whitespace padding" I proposed work for you? It may actually be more practical as it would only affect this particular view (and we suppose it's some kind of an admin tool anyway, so we don't care if it sends a few extra kilobytes of data) – Sergey Jun 15 '16 at 00:22
  • No. the 'whitespace padding' didn't work. Everything rendered all at once. – fat fantasma Jun 15 '16 at 00:24
  • This IS an admin only page, but this idea seems pretty hacky. In particular I'm concerned with the end-user (whom I will have limited contact with in future) becoming impatient and re-clicking, as they would not have any (much?) feedback in this case. – Ng Oon-Ee Jun 21 '16 at 02:16
  • I'm not sure what you mean by "no feedback"... There will be nothing to re-click: after submitting the form the user is presented with a blank page, on which a new line appears every few seconds: "Still working, 42% done, about 10 minutes remain" - you're free to choose the format of the message. After the process is completed the page will also say so and provide the link to view the updated data. Seems like pretty good feedback to me... – Sergey Jun 21 '16 at 02:37
0

So is the long running process triggered by browser action? I.e., the user is uploading the CSV that gets processed and then the view is doing the processing right there? For short-ish running browser processes I've used a loading indicator via jQuery or javascript, basically popping a modal animated spinner or something while a process runs, then when it completes hiding the spinner.

But if you're getting into longer and longer processes I think you should really look at some sort of background processing that will offload it from the UI. It doesn't have to be a message based worker, but even something like the end user uploads the file and a "to be processed" entry gets set in a database. Then you could have a pyramid script scheduled periodically in the background polling the status table and running anything it finds. You can move your file processing that is in the view to a separate method, and that can be called from the command line script. Then when the processing is finished it can update the status table indicating it is finished and that feedback could be presented back to the user somewhere, and not blocking their UI the whole time.

Peter Tirrell
  • 2,962
  • 4
  • 29
  • 52
  • Your second paragraph is interesting, but I don't think there's a way to present to the user without using some form of auto-reload option on the page (or every page) the user is currently on? Would require client-side polling, in which case I'd basically be doing AJAX? – Ng Oon-Ee Jun 21 '16 at 02:14
  • Yeah, I would probably either have some sort of AJAX-y background javascript giving a waiting indicator or some sort of progress indicator, *or*, I'm thinking sort of like how YouTube does it. Where you upload a file, you see an upload progress meter, then it's "done". They're doing background processing on the file and there's a status page where you can see some indication, but you don't have to wait at the initial upload for their processing to finish. – Peter Tirrell Jun 27 '16 at 13:10