Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Race condition: running_check, performance concern #21

Open
argoneuscze opened this issue Oct 21, 2018 · 5 comments
Open

Race condition: running_check, performance concern #21

argoneuscze opened this issue Oct 21, 2018 · 5 comments

Comments

@argoneuscze
Copy link
Contributor

argoneuscze commented Oct 21, 2018

Here:

def running_check(self, runner):
self.runtime += 1
if self.runtime % 60 == 0:
for area in self.area_manager.areas:
print(area.last_talked)
area.last_talked = None
self.stats_manager.save_alldata()
Timer(1, self.running_check, [runner]).start()
def load_runner(self):
self.runner = threading.Event()
self.running_check(self.runner)

A thread changes area.last_talked, which is later changed here, but in a separate thread.
if not self.client.area.basement:
if self.client.area.last_talked is None:
self.client.area.last_talked = self.client.ipid
self.server.stats_manager.char_talked(self.client.char_id, self.client.ipid, self.client.area.status)

The timer, if anything, should work asynchronously sharing the same asyncio event loop as the protocol code. This way there is a race condition where you are reading/writing a value in two separate threads.

Furthermore, you're creating a new Timer every second instead of reusing one, which is going to be slow, as there is a new thread spawned and killed every second.

Also, the runner variable doesn't seem to do anything. What is the point of it?

I also suggest removing the timer entirely and creating a solution that compares timestamps instead of a timer that runs every second.

@argoneuscze
Copy link
Contributor Author

argoneuscze commented Oct 21, 2018

Also as a note, you're writing three different files every second in the same function with self.stats_manager.save_alldata(). At that point you might want to switch to an actual database, but that's out of the scope of this issue.

EDIT: Correction, it saves the files every minute, still.

@oldmud0
Copy link

oldmud0 commented Oct 21, 2018

Remark: GIL prevents serious concurrency violations from occurring, but I see what your point is - threading is not necessary to solve this problem.

@argoneuscze
Copy link
Contributor Author

Relying on the GIL is ugly to say the least. The GIL is not something that's specified somewhere, it's an implementation detail of CPython - what if they're using another implementation? What if someone decides to port the server to Cython eventually?
For example Jython or IronPython don't even have a concept of GIL.

@oldmud0
Copy link

oldmud0 commented Oct 21, 2018

You can still have a concurrency violation even with the GIL. Two coroutines concurrently modifying a complex shared resource may result in unintended behavior.

@argoneuscze
Copy link
Contributor Author

argoneuscze commented Oct 27, 2018

Looks a bit better now, but there's still some things:

async def load_runner(self):
asyncio.ensure_future(self.running_check())

This function doesn't need to be async - all it does is create (and return) a new task in the event loop, which isn't an asynchronous action.

And if that one is not asynchronous, this line is redundant:

loop.run_until_complete(self.load_runner())

That's all for the asynchronous part, as far as I can see.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants