Skip to content

Conversation

@shinrich
Copy link
Member

This is derived as a subset of the H2 to origin PR #7622. This PR adds no support for H2 to origin, but it performs the restructuring so the HttpSM refers to the server connection via a ProxyTransaction instead of a PoolableSession.

This was requested by @maskit in hopes this work would assist them in discovering the cause of the server session leak described in issue #7471. This restructuring should simplify server connection leaks because the server_txn object stays around until HttpSM::kill_this() when server_txn->transaction_done() is called. If the server session should be returned to the origin pool, it will have previously been marked so. Otherwise the server session will be closed. Similarly earlier calls to do_io_close on the server transaction will be held until transaction_done() is called.

In the case of an origin connection failure and subsequent retry, server_txn->transaction_done() is called in HttpSM::do_http_server_open() before another connection attempt is made.

The PR also includes some stats I added to help debug a pool buildup issue I ran into debugging this PR against our 9.1.x build in master.

I also pulled into the following commits to make the move to our 9.1.x branch from master easier.

PR #6241 - but I see that just got picked into 9.1.x already
PR #7571 - I think that one is there now too.
PR #7584 - which also just got pulled into 9.1.x
PR #7600

@shinrich shinrich added this to the 10.0.0 milestone May 17, 2021
@shinrich shinrich requested review from a-canary, maskit and vmamidi May 17, 2021 21:06
@shinrich shinrich self-assigned this May 17, 2021

#include "HttpSessionManager.h"
class EThread;
class Continuation;
Copy link
Member Author

Choose a reason for hiding this comment

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

Needed to avoid pulling in unnecessary stuff to the unit test.

{
return lhs == rhs;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Promoted the conntecting tracking calls to a super class.

}

void
Http1ServerSession ::release_transaction()
Copy link
Member Author

Choose a reason for hiding this comment

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

Called from transaction_done to adjust the released_transacations count and cause any pending close or server session pool release to occur.

HttpTunnelProducer *p = nullptr;

if (!server_entry || !server_entry->vc || !server_session || !server_session->get_netvc()) {
if (!server_txn || !server_txn->get_netvc()) {
Copy link
Member Author

Choose a reason for hiding this comment

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

May need to adjust this test, since server_txn will not be null at this point even if we did already call do_io_close on it. The get_netvc() may still be returning null, so it may still be ok.

@shinrich
Copy link
Member Author

[approve ci autest]

@shinrich
Copy link
Member Author

[approve ci clang-analyzer]

}

void
ServerSessionPool::testSession(PoolableSession *ss)
Copy link
Member

Choose a reason for hiding this comment

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

It seems like this functions is unnecessary. It's called by Http2ServerSession::test_session() on #7622, but Http2ServerSession::test_session() is not called.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this one can be removed.


sockaddr const *get_remote_addr() const;

virtual HTTPVersion get_version(HTTPHdr &hdr) const;
Copy link
Member

Choose a reason for hiding this comment

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

We don't need this at the moment.

SinceProxySession::get_version receives HTTPHdr, a proxy session can always return correct version. I wonder if there will be a session that has multiple versions of transactions. I'm not sure if we are allowed to use multiple versions on a traditional HTTP connection like below:

[Connect]
GET /a HTTP/1.0

GET /b HTTP/1.1
Host: example.com

[Close]

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure CONNECT makes my head hurt. I would think that only one nested method would be interpreted over the CONNECT method. And ATS would only interpret it if the CONNECT was made over a non-TLS connection. Otherwise ATS treats the body as a blind tunnel.

Will have to read the RFC tea leaves on this.

}

void
HttpSM::create_server_txn(NetVConnection *netvc, PoolableSession *new_session)
Copy link
Member

Choose a reason for hiding this comment

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

This function is really confusing.

The name implies that we call this function whenever we want to create a server transaction, but that is probably incorrect. This function seems to be only used for the initial server transaction on a session.

Furthermore, this is a public function despite that HttpSM has to be the only caller. Anybody can accidentally call this function based on the wrong assumption above.

Can you make this a private function and rename it to create_initial_server_txn? I haven't understood why server transactions have to be created here and there, and I think it should be done at only one place, but the name would tell that is the current design.

Copy link
Member Author

Choose a reason for hiding this comment

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

create_initial_server_txn is probably a better name. And it can be made private. The goal of the function is to wire in the netvc into a ProxyTransaction object (Http1ServerTransaction in this case). It is taking logic that was inline in the HttpSM::state_http_server_open function. It is called a bit later once H2 to origin is introduced because we need to wait to see what protocol is negotiated to create the correct subclass of ProxyTransaction.

Copy link
Member Author

Choose a reason for hiding this comment

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

Reworked so create_server_txn is called in HttpSessionManager too instead of calling new_transaction directly

}

bool
ProxyTransaction::is_read_closed() const
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this used in some subsequent PRs? Seems a little odd to have this "no-op" method, which is never called as far as I can tell.

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably leftover from the H2 to origin pull back.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed for now. Will be added back with the H2 to origin logic.

} else {
ink_assert(data && data == ka_vio);
ink_assert(read_state == HCS_KEEP_ALIVE);
if (data) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this possibly read nicer as a switch statement?

Copy link
Member Author

Choose a reason for hiding this comment

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

Cannot have variables as case elements.

(int)http_origin_close_private, RecRawStatSyncCount);
RecRegisterRawStat(http_rsb, RECT_PROCESS, "proxy.process.http.origin.raw", RECD_INT, RECP_NON_PERSISTENT, (int)http_origin_raw,
RecRawStatSyncCount);

Copy link
Contributor

Choose a reason for hiding this comment

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

This is great!

void
HttpSM::set_server_txn(ProxyTransaction *txn)
{
ink_release_assert(server_txn == nullptr);
Copy link
Contributor

Choose a reason for hiding this comment

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

I would probably argue that this should be a debug assert, it kinda feels like we're too liberal sometimes with the release assert. Looking where this is called, it can't be nullptr (assuming that to_return->new_transaction() is not allowed to fail).

Copy link
Member Author

Choose a reason for hiding this comment

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

Agree. I use the release_asserts liberally while debugging in production. Don't always get them cleaned up. Looking forward to using your enable-all asserts option.


if (!try_reuse) {
HTTP_INCREMENT_DYN_STAT(http_origin_make_new);
if (TS_SERVER_SESSION_SHARING_MATCH_NONE == t_state.txn_conf->server_session_sharing_match) {
Copy link
Contributor

@zwoop zwoop May 18, 2021

Choose a reason for hiding this comment

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

This code is slightly alarming; It feels like the server session would be in one of several conditions, as seen by which metric we increment. But, the logic to decide what "state" it is in is based on a number of conditions and member variables, rather than a single "state". That feels like e.g. someone could set "raw" and "is_private", and the session is now in a unknown state.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added these stats for debugging to solve my last issue before posting this PR. No doubt could be tidied up. Or removed entirely.

server_session = nullptr;
if (server_entry) {
server_entry->vc = nullptr;
server_entry->read_vio = server_entry->write_vio = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

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

This would look a lot better if you don't do the double assignment on line 5541 :).

m_ip_pool.erase(to_return);
this->removeSession(to_return);
} else {
if (first != m_fqdn_pool.end()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Bike shedding, but for easier read, why not make this an } else if { ?

ts::LocalBufferWriter<256> w;
w.print("[{}] new connection, ip: {}, group ({}), count: {}\0", con_id, get_server_ip(), *group, group->_count);
Debug("http_ss", "%s", w.data());
if (state == SSN_CLOSED) { // Already been closed
Copy link
Member

Choose a reason for hiding this comment

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

I prefer this way. It has less dup code.

if (state != SSN_CLOSED) {
  state = SSN_CLOSED
  release outbound connection tracking
  close vc
  decrement stats
}

if (transact_count == released_transactions) {
  this->destroy();
}

Copy link
Member

@maskit maskit left a comment

Choose a reason for hiding this comment

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

Although I can't say this doesn't break things, but it looks good to me. I'm going to approve this when conflicts are resolved.

@shinrich shinrich force-pushed the h1-origin-txn-restructure branch from 2e5a4ed to 7e9a61c Compare June 2, 2021 14:36
@shinrich
Copy link
Member Author

shinrich commented Jun 2, 2021

[approve ci autest]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment