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

ensure connection is released #415

Closed
wants to merge 11 commits into from

Conversation

thehesiod
Copy link
Contributor

@thehesiod thehesiod commented Jan 4, 2018

I've seen cases where the close can throw psycopg2.ProgrammingError: close cannot be used while an asynchronous query is underway which would previously cause the connection to not be returned to the pool, and cause a connection to "leak" from the pool. See discussion in: #364

This also fixes running tests on OSX

@jettify
Copy link
Member

jettify commented Jan 4, 2018

Good point, but should we also close connection in case we have any error on cursor.close(), this way pool will recycle it, I am not 100% sure that connection is usable after that exception.

@thehesiod
Copy link
Contributor Author

@jettify but that's what the line that failed tried doing, closing it, and from what I saw on timeouts it tries to cancel the connection as well.

@thehesiod
Copy link
Contributor Author

thehesiod commented Jan 4, 2018

btw CI seems to be failing due to something I did not change:

    writing manifest file 'pip-egg-info/psycopg2.egg-info/SOURCES.txt'
    Error: could not determine PostgreSQL version from '10.1'

@jettify
Copy link
Member

jettify commented Jan 4, 2018

Regarding CI, I should have fix soon.

@jettify
Copy link
Member

jettify commented Jan 4, 2018

Failed line tried to do cursor.close()? but not con.close() or I am missing something?

@jettify
Copy link
Member

jettify commented Jan 4, 2018

Rebase should fix CI

@codecov
Copy link

codecov bot commented Jan 4, 2018

Codecov Report

Merging #415 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master     #415   +/-   ##
=======================================
  Coverage   93.53%   93.53%           
=======================================
  Files          25       25           
  Lines        3651     3651           
  Branches      194      194           
=======================================
  Hits         3415     3415           
  Misses        198      198           
  Partials       38       38

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7da9004...2cadd1d. Read the comment docs.

@thehesiod
Copy link
Contributor Author

btw, let me know if you'd like to handle this via https://docs.python.org/3.5/library/contextlib.html#contextlib.ExitStack, I use it for situations like these to avoid code bloat

@jettify
Copy link
Member

jettify commented Jan 5, 2018

I like ExitStack too but change is not significant probably not worth it.

@jettify
Copy link
Member

jettify commented Jan 5, 2018

Regarding change, as you mentioned in other issue, we probably need to close connection, then return is back to the pool. Pool will drop it:

aiopg/aiopg/pool.py

Lines 250 to 261 in b301c9c

if not conn.closed:
tran_status = conn._conn.get_transaction_status()
if tran_status != TRANSACTION_STATUS_IDLE:
logger.warning(
"Invalid transaction status on released connection: %d",
tran_status)
conn.close()
return fut
if self._closing:
conn.close()
else:
self._free.append(conn)

@jettify
Copy link
Member

jettify commented Jan 5, 2018

I worry about returning potentially broken connection to the pool.

@thehesiod
Copy link
Contributor Author

ok, so then what we need to do is create a new connection for the pool, let me c if I can come up with something

@jettify
Copy link
Member

jettify commented Jan 5, 2018

no need to create connection, pool should care about that
on each acquire we call following function just to see, may be we have free slots for new connections

aiopg/aiopg/pool.py

Lines 200 to 216 in b301c9c

while self.size < self.minsize:
self._acquiring += 1
try:
conn = yield from connect(
self._dsn, loop=self._loop, timeout=self._timeout,
enable_json=self._enable_json,
enable_hstore=self._enable_hstore,
enable_uuid=self._enable_uuid,
echo=self._echo,
**self._conn_kwargs)
# raise exception if pool is closing
self._free.append(conn)
self._cond.notify()
finally:
self._acquiring -= 1
if self._free:
return

@thehesiod
Copy link
Contributor Author

with minsize == maxsize == 1:

If we don't call release, and the connection never"closes" we're stuck because there are no "freeable" connections (default recycle is -1).

If we do call release (like this patch does), and the connection never closes, we append a potentially bad connection to the pool.

So I think we need "drop" ability to Pool.release

@jettify
Copy link
Member

jettify commented Jan 5, 2018

I mean close connection and release it back to the pool. Regardless of pool size connection should be always returned, it could be closed but should be returned.

@jettify
Copy link
Member

jettify commented Jan 5, 2018

drop ability is here:

aiopg/aiopg/pool.py

Lines 249 to 250 in b301c9c

self._used.remove(conn)
if not conn.closed:

we stop tracking connection and do not move it to free connection list

@thehesiod
Copy link
Contributor Author

I'm not sure I'm following what you're saying but I've submitted a change which will allow non-closed connections to be dropped, let me know what you think.

aiopg/pool.py Outdated
if self._closing:
conn.close()
else:
elif conn.closed:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate here? bit not follow in what case we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i just realized the fundamental premise of this function is to reuse connections, so there's a problem when we're passing connections that are in a bad state (thus I reverted last change).

Here's the issue which we need to resolve: conn.close() consistently throws an exception, how do we drop it? Right now we're going to call release, which will add it back to the free pool. If we don't call release it will be stuck in the used pool.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hold on but I thought that self._cur.close() throws exception in your case not conn.close() ?

@jettify
Copy link
Member

jettify commented Jan 5, 2018

I am thinking more about:

    def __exit__(self, *args):
        try:
            self._cur.close()
        except ProperException as e:
            self._conn.close()
        finally:
            try:
                self._pool.release(self._conn)
            finally:
                self._pool = None
                self._conn = None
                self._cur = None

@thehesiod
Copy link
Contributor Author

ah, I was assuming that if the cursor close fails the connection close would fail as well, but ya I guess not, I'll code that up, thanks

@jettify
Copy link
Member

jettify commented Jan 5, 2018

Glad to see you find some time to code between your parental responsibilities :)

@thehesiod
Copy link
Contributor Author

thanks, just sent you a link in gitter :)

aiopg/utils.py Outdated
@@ -145,7 +146,7 @@ class _PoolAcquireContextManager(_ContextManager):
__slots__ = ('_coro', '_conn', '_pool')

def __init__(self, coro, pool):
self._coro = coro
super().__init__(coro)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about this one, constructor also sets _obj property, it is not listed in slots, probably better have original version

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, this seems more correct, _obj is used for __aenter__ / __aexit__ in the parent class, and this is overriding the behavior and thus should be using the same attribute

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with even a further simplification :)

@jettify jettify mentioned this pull request Jan 9, 2018
@jettify
Copy link
Member

jettify commented Jan 10, 2018

merged as part of #421

@jettify jettify closed this Jan 10, 2018
@thehesiod thehesiod deleted the thehesiod/close-fix branch January 19, 2018 02:12
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

Successfully merging this pull request may close these issues.

None yet

2 participants