Transaction expiring randomly in background task

What I’m trying to do:

When my application starts up, I kick off several background task to load multiple data tables. I have a central page timer that then loops through the task to check when they are complete, to add each task return to data manager for that session.

Each of these background task search different data tables with different queries, serialize the data and returns the serialized data.

@authenticated_callable
@anvil.server.background_task 
@anvil.tables.in_transaction
def get_tabular_companies_background_task(status="Active",start=0,finish=5):
  print(f"SEARCHING FOR {status} COMPANYs \n start: {start} finish: {finish} ")
  if start is None:
    print(f"start: {start}")
    companys = app_tables.driver_companys.search(status=q.full_text_match(status))
  elif finish is None:
    print(f"finish: {finish}")
    companys = app_tables.driver_companys.search(status=q.full_text_match(status))[start:]
  else:
    print(f"start: {start} finish: {finish} ")
    companys = app_tables.driver_companys.search(status=q.full_text_match(status))[start:finish]
    
  print("STOPPED SEARCHING")
  result = schema.dump(companys, many=True)
  print("STOPPED DUMPING")
  result = [dict(item,**{'id':item['_id']}) for item in result]
  print("STOP ADDING IDS")
  if not result:
    print("EMPTY")
    return []
  print(result)
  return result  

What I’ve tried and what’s not working:

I call two background calls (one for status = “Active” and one for status = “Prospect”) and I randomly yield the following error:

So the process completes (as seen by the STOP ADDING IDS print out) but, the transaction expires before the data is returned.

Questions

Is there an issue with decorating the background task with in_transaction?

Does it make more sense to separate out the table search in its own function decorated by the in_transaction function?

Are other server functions still ran in the background session if called by a background task?

Thanks!

Idon’t understand what happens when you do schema.dump, but my guess is that a reference to a row generated by the search is stored somewhere, either by that schema.dump or it’s returned, then the in_transaction decorator exits, then one of those rows is used after the transaction has exited and Anvil doesn’t like it.

Each background log session ends when the decorated background task ends.

1 Like

Yes.

As a general rule, you do want your transaction functions to be pretty tightly focused on just the database actions in the transaction. Separating them out into separate functions that are decorated by in_transaction is the usual technique. That helps minimize false positives in the transaction handling.

I don’t see where you need a transaction, though. The only database action you’re doing is a single search. Generally, you need transactions when you have a series of actions that must either all succeed or all fail.

1 Like

schem.dump is a method in the anvil_extras serialization library. It effectively converts rows to dictionaries (including linked rows).

Documentation here:

https://anvil-extras.readthedocs.io/en/latest/guides/modules/serialisation.html

Thank you!

I thought transactions were used as a way to ensure multiple users were not reading/writing to the same data row at the same time.

For my use case

I want to prevent USER A from writing to data table as USER B is pulling that data from the table.

Is this an incorrect use? or maybe over use of the transaction decorator?

I did not know about this use case! Thank you!

I had a quick look at the source code of schema.dump and didn’t help. I still don’t know what it does (because it’s using another external library).

Why does your background task end with return result?
Background shouldn’t return anything. They should do their job and store the result either in the task task_state or in a data table.

Perhaps the fact that you are returning some database rows created in the transaction causes those rows to be used by some piece of the background task infrastructure and causes the transaction failure.

1 Like

I narrowed the scope to the get method and that resolved my issue! Thank you all :slight_smile:

1 Like

I was using the get_return_value method of the task object. This returns anything the task returns, or None if the task is incomplete.

Is it better to use the state vs the return value? I am pinging the task in a timer for get_termination_status() and calling the return value. I didn’t know if using the state was better practice.

Here is the code:

def check_data_is_loaded(self):
      with anvil.server.no_loading_indicator:
        term_status = None
        term_status = self.task.get_termination_status()
        if term_status == "completed":
          data = self.task.get_return_value()
          if data is not None:
            self.set_data(data)
            self.complete_init_flag = True   

This is called by a timer and I reset term_status to None, because I was noticing it still being set to a value when being called.

Oh, yes, that’s a very good way! I just never use it and forgot about it.

I also never used schema.dump and I don’t know if it keeps references to the row objects.

I also don’t understand why you would need to term_status = None. The scope of term_status is that function, so when the function starts it shouldn’t have a value. And if there is one global variable with the same name, it shouldn’t work without global term_status.


Look at that: 3 statements saying I don’t know something, answering a question that already has a solution!
I better get back to working now!

1 Like

Just want to poke in here to say, many background task uses have noticed inconsistent behavior when using anything other than the way you are doing it here in this part of your code.

The link below is just one example where this was discussed:

2 Likes

Wish I could say I was smart, but I definitely leveraged that information :slight_smile: