Running scheduled tasks at time stored in data table

I am building a photo contest website with bitcoin prizes awarded to lucky voters. Here is a link to the work in progress build: https://votegoodlooks.anvil.app/

Each election has a start and end date/time, and I would like to trigger the tallying of the ballots and allocation of the prizes when the election period ends.

I saw the Server Side Scheduled Jobs post and the suggestion to use https://www.setcronjob.com/ to periodically check if there are any elections that have recently ended will probably work, but since the elections may not take place in regular intervals it seems like it isn’t the ideal solution.

Would a constantly running background task be suitable? Or should I explore other techniques like checking if there are recently finished elections each time a user clicks on the results page and do the tallying in a just-in-time fashion?

That’s a very nicely styled app!

Can you update the tally each time a user submits their votes, so it’s always correct?

Then in the results page you can query the elections based on end time:

finished = app_tables.elections.search(end_time=q.less_than(datetime.now()))

thank you!

I looked more into cronjobs and it is exactly what I want to do and it was terribly easy to implement with https://cron-job.org

here is my solution, for anyone in the future that might be searching “scheduled tasks”, “endpoint credential authorization”, " “TypeError: Unicode-objects must be encoded before hashing”

@anvil.server.http_endpoint("/check_elections",require_credentials=True)
def check_elections():
    email = anvil.server.request.username
    password = anvil.server.request.password
    admins = app_tables.users.search(admin=True)
    for admin in admins:
        if email == admin['email'] and bcrypt.hashpw(password.encode('utf-8'),
                                                     admin['password_hash'].encode('utf-8')) == admin['password_hash']:
            completed_elections = app_tables.election.search(END=q.less_than(datetime.datetime.now()))
            for election in completed_elections:
                if election['WINNERS'] is None:
                    election['WINNERS'] = calculate_winners(election['RESULTS'])
3 Likes

This is fantastic! I will definitely be referring to this at some point. Thanks for the post.

Thanks for the sample, I intend to use cron-job.org at some point, but haven’t gotten there yet. I’m curious, though, you pass the admin email and password in via the endpoint call. It doesn’t look like calling the endpoint can cause any harm (it only calculates winners for completed elections, and then only if the winner hasn’t already been calculated).

Does it need to be protected by an email and password? I ask because that’s a level of exposure for the credentials.

I’d always intended for my eventual cron endpoints to be okay to call at any point, and to not require credentials. Now I’m wondering if I’m missing something that I need to rethink.

1 Like

hmm, that’s a great point. I think you are right. I probably don’t need to authenticate credentials in this case.

At first, it felt like good practice to protect the endpoint, but when you put it that way, perhaps the risk profile of exposing my credentials to cron-job.org is greater than if a hater was to bombard this endpoint.

thanks for the help thinking through this.

Although there appears to be no material difference between the winner selection being triggered by a malicious/curious person/bot instead of our scheduled task, I decided to keep the authentication but only from an account that has no other admin capabilities other than authenticating chron jobs.

This will help prevent endpoint funny business while reducing the consequence of a potential chron-job.org breach of trust.

Since I am taking other measures to ensure provable fairness it feels right to only allow winner selection to take place on our command.

thanks for prompting this solution.

2 Likes

Can Anvil Scheduled Tasks run scheduled tasks at time stored in data table?
I haven’t found a way to do so since what available is to run every minutes/hours …

No as far as I know.
What about a scheduled task running every minute or hour and check a table to decide what jobs to run?

1 Like

Hi @stefano.menci

12 times a day, an action is to be done at a specific time. The task can be done by an authorised personel clicking a button to triger code. Scheduled task running every minute is kind of overkill since there are a lot of data while every hour doesn’t meet the requriment.

Checking a table row every minute and doing nothing is not an heavy load on the server.

I should have added that a lot of calculations are to be done as well

True, @stefano.menci, if only one app is doing it. But over many apps, all the behind-the-scenes work adds up: firing up a (light) vm, loading the interpreter, loading the source code, connecting to the database, querying a database table, … to complete a test that will fail 99% (or more) of the time.

Compare that to running one task, and then only when needed.

If your traffic is 1 request per minute, then adding this background task will double the server load (actually less, because real requests will do much more than checking one row on a table) and reach 2 requests per minute. This is a very very very low load on the server.

If your traffic is hundreds of requests per minute, then adding this background task will add less than 1% to the load.

I agree, it would be nice to be able to schedule tasks from code and it doesn’t feel right to have this task running so often doing nothing. But I use Anvil because it makes my life easy, not the server life. If I wanted to be nice on the server I would work in C++ rather than Python, I would have my own server and manage the full stack, and save 90% of the load. Instead I work in the very slow Python and I leave everything else to the Anvil team. I end up with apps served from UK and used by US clients. That’s really horrible, much worse than wasting 0.1 second per minute, but I love it because it saves 90% of my time.

PS: If you keep the server running then the interpreter doesn’t need to fire up every time.

2 Likes

I just found that the solution is actually quite easy to implement.
Run task every minute and compare current time with the ones in the database

I cannot mark this as the solution