@João, you are programming yourself into a corner. No matter what you do,
you’ll keep running into more problems. The reason for this is because you’re
trying to do atomic operations at the application level. You simply can’t do
that.
If you check whether or not something is locked at the application level, and
then take some action, you risk making a mistake. What happens if the lock
state changes between when you checked, and the next operation in your program?
You will take the wrong action. By definition, *anything* can happen between
*any* two operations in your program. Another process could change the state:
1: if databaseislocked() then:
2: doseomthing()
3: else:
4: dosomethingelse()
What if the database state changes between 1 and 2 or 4? Perhaps you think if
it ends up being locked, and you try to lock it again, then your subsequent
attempt will fail. But then what? Do you go into to a loop trying to lock it?
Now you can get blocked waiting for a lock, or worse, get into a deadlock
situation, with two processes blocked, each waiting for the other one to free
up something.
So you say you created a flag? Now you’ve just pushed the problem back onto
another application-level ‘lock’ that can’t possibly be relied upon (unless
you’re using a low-level locking mechanism, and even then this can be tricky).
My understanding of what I read between you and Anthony is this:
a) SQLite defers starting the actual transaction until the first write, so your
initial code wasn’t actually wrapped in a transaction at all.
b) As a result, your tests failed. But this doesn’t mean transactions don’t
work in SQLite, or in general.
c) Anthony tried to force the transaction open with the FOR UPDATE clause, but
that doesn’t work in SQLite.
d) So the solution for SQLite is to explicitly start the transaction before
reading and/or writing anything.
So all you have to do is
db.executesql('BEGIN IMMEDIATE TRANSACTION')
before you start reading and updating your counters, and the work order, and
then
db.commit()
as soon as you’re done. You *do not* need to check if the database is already
locked. SQLite will handle this during the commit process. It will guarantee
that any two operations running concurrently are isolated from each other—each
process will see a consistent view of the database, and the final result will
be the same as if they had executed one after the other (serially). If they
collide, SQLite will abort one of them, and restart it. For this to work, it’s
important that:
1. You start the transaction before you *read* anything, not just before you
write. SQLite needs to know what you’re looking at, as well as what you intend
to change.
2. You do the whole process in batch. You can’t stop to wait for the user
to do something. Get all the input you need from the user first, then do the
update without stopping.
This process will be more scalable, and not require locking the whole database,
on a multi-user MVCC database like Postgres. This would also avoid the
silliness with the SQLite adapter bugs. We use Postgres with Nginx and Web2py
in production systems with lots of contention and it works beautifully. But
this really only matters if you have lots of users. SQLite is very solid for
smaller, single-server deployments.
John
From: [email protected] <[email protected]> On Behalf Of João Matos
Sent: Sunday, March 24, 2019 2:47 PM
To: web2py-users <[email protected]>
Subject: [web2py] Re: Does web2py/DAL allow for creating atomic operations
(begin transaction/end transaction)?
@John
I think you didn't understand the problem.
I have to assign a wo_counter and a sn_counter to each new record of the wo
table.
To do that, I must disable any attempt to change them during the process of
saving the wo record to the database.
That process consists of:
1. If the user didn't select an existing wo_counter, then I must read the last
assigned wo_counter, increase it, save it in memory to assign it to the wo
record, and save the new value in the wo_counter table.
2. Read the last assigned sn_counter (which is different by equipment_id
selected by the user), increase it, save it in memory to assign it to the wo
record, and save the new value in the sn_counter table.
3. Save the wo record with the wo_counter and sn_counter that were stored in
memory.
The existing transaction auto-created by web2py within each action does not
allow the protection I need. I tested it. If you want you can follow this test
procedure I used.
Put a sleep command between step 1 and 2.
Open session 1 where the wo record procedure I listed above is running until it
pauses in the sleep command.
On session 2 access the table for the wo_counter or sn_counter. Change the
value of the counter and save it.
Wait until the pause ends on session 1 and the wo record function will save
it's value over the value stored by the session 2.
This means that information was lost.
What is needed and works with Anthony's solution is to force the lock of the
database (this is a limitation of SQLite, it would be a better solution to lock
only the table or even better only the record in question).
This way I'm sure that if someone else tries to change the counters while a
session is changing them to save a record, they will not succeed.
I tested Anthony's solution and it does work because when using the same test I
explained above, the 2nd session is not able to change the record. SQLite
returns an error because the db is locked.
This is exactly what I need.
The only thing missing is a way to check if the SQLite db is in locked state or
not.
Until now I couldn't find any way to check that, so I'm complementing Anthony's
solution with a flag and check for the flag on the wo_counter and sn_counter
edit/delete actions.
The reasons for not using the auto-increment id field auto-created by the
web2py for the wo table as a wo_counter is both legal and format related.
The sn_counter because it depends on the equipment_id makes it impossible to
use the auto-increment id field auto-created by the web2py.
domingo, 24 de Março de 2019 às 18:29:41 UTC,
[email protected]<mailto:[email protected]> escreveu:
João,
No, SQLite transactions are not only ACID, the Isolation is Serializable, which
means even if the execute concurrently, the result should be same as if they
executed in series (on after the other). What @Anthony describes should not be
necessary, since you already in a transaction.
https://www.sqlite.org/transactional.html
https://www.sqlite.org/atomiccommit.html
If everything is set up correctly, it should work. Please note that both
sessions must be in a transaction to guarantee a consistent result. You can't
just go change the database while the transaction is pending. That can fail.
This is the purpose of transactions.
Also please note that this is probably note the best way to do this. SQL has
auto-increment fields that are good for assigning numbers to things, such as
unique IDs to records. Web2py automatically creates an auto-increment ID field
for all your tables. So if you have a table for Items, let's say, the ID field
in that table will already be a unique number for every new record, which you
could use for your serial number. Just insert a new record to get a new ID.
If you don't want to use the built-in ID, you can create your own
auto-increment field.
If you have a table for Work Orders, it will already have an ID field that's
automatically set to a new unique number for each record. Just insert a new
record to get a new number. If you don't want to use the ID field as your work
order number, you can create your own field. No need to read, update, and
store counters.
John
On Thursday, March 21, 2019 at 3:18:20 PM UTC-7, João Matos wrote:
@Leonel
Maybe I'm not explaining myself correctly.
I tested and it isn't working the way I need and explained.
My test was:
I put a sleep(10) between step 1 and 2.
On another session I changed the record in question. Checked the changed was
done.
Waited for the sleep to end and then step 2 wrote over the change I made on the
other session.
The end result is that the transaction isn't protecting (locking) the records
in question, which is what I need.
quinta-feira, 21 de Março de 2019 às 13:11:27 UTC, Leonel Câmara escreveu:
The transaction is atomic, that means there's nothing happening in the middle.
Your use case is fine. Of course, that if you give the user a form, while he's
editing it, the form can be changed by others as well, in that case you need to
add your own locking or check the record for modifications while the user was
editing the form.
--
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
---
You received this message because you are subscribed to the Google Groups
"web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to
[email protected]<mailto:[email protected]>.
For more options, visit https://groups.google.com/d/optout.
--
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
---
You received this message because you are subscribed to the Google Groups
"web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.