Fix deadlock in ChannelManager's handle_error!() - #568

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock
Apr 2, 2020
Merged

Fix deadlock in ChannelManager's handle_error!()#568
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

ChannelManager fails backward any pending HTLCs upon channel failure. A deadlock occurs in such cases since handle_error!() takes a locked channel_state and finish_force_close_channel() attempts to reacquire the lock. This PR adds a test to demonstrate the deadlock and fixes it by holding the lock for shorter scopes.

Fixes#549.

@jkczyz
jkczyz requested a review from TheBlueMattApril 1, 2020 05:31

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, mod the one comment and the commits being "out of order" - we should first fix the issue, then add the test as otherwise we have a state in git history that fails to pass tests.

Comment threadlightning/src/ln/functional_tests.rs Outdated
}

// Alice -> Bob -> Chuck: Route another payment but now Bob waits for Chuck's earlier revoke_and_ack.
let (_, failed_payment_hash) = route_payment(&nodes[0], &[&nodes[1]], 50_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oh lol, guess you dont need three nodes for this test. Note that this is a completely separate payment from the next one - we disambiguate by HTLCSource, not the payment_hash (as otherwise there are a number of privacy and practical funds issues). The payment_failed at the end is the indicator - its saying that a payment nodes[1] tried to send failed, not that it should fail back the HTLC to nodes[0].

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Groking: what is "the next one" referring to?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

That was a reference to the manual send_payment call two lines down.

Ah, I see. This was my attempt at simulating nodes[2] not sending revoke_and_ack.

If I only need two nodes, then are you saying I can get rid of the nodes[0] to nodes[1] part entirely (i.e., the places where I'm using route_payment)? In that case, what is being "failed backward"?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yep. The part being "failed back" is purely the event telling us that the payment failed (as the lock in question here is taken before we decide if the HTLCSource is an OutboundRoute which we sent or a PreviousHopData which means someone else sent us an HTLC that we relayed).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, simplified this by only using two nodes in the test.

@TheBlueMatt

TheBlueMatt commented Apr 1, 2020 via email

Copy link
Copy Markdown
Collaborator

TheBlueMattand others added 2 commits April 1, 2020 16:27
This partially reverts 933ae34,
though note that 933ae34 fixed a
similar deadlock while introducing this one.
If we have HTLCs to fail backwards, handle_error!() will call
finish_force_close_channel() which will attempt to lock channel_state
while it is locked at the original caller. Instead, hold the lock for
shorter scopes such that it is not held upon entering handle_error!().
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Jeffrey Czyz <jkczyz@gmail.com>
Upon channel failure, any pending HTLCs in a channel's holding cell must
be failed backward. The added test exercises this behavior and
demonstrates a deadlock triggered within the handle_error!() macro. The
deadlock occurs when the channel_state lock is already held and then
reacquired when finish_force_close_channel() is called.
@jkczyz
jkczyzforce-pushed the 2020-03-handle-error-deadlock branch from 7e25d66 to 3968647CompareApril 1, 2020 23:37
@TheBlueMatt
TheBlueMatt merged commit f0b037c into lightningdevkit:masterApr 2, 2020
Sign up for freeto 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.

Deadlock in handle_error!() on HTLC fail-back

2 participants

@jkczyz@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix deadlock in ChannelManager's handle_error!() - #568

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock
Apr 2, 2020
Merged

Fix deadlock in ChannelManager's handle_error!()#568
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

ChannelManager fails backward any pending HTLCs upon channel failure. A deadlock occurs in such cases since handle_error!() takes a locked channel_state and finish_force_close_channel() attempts to reacquire the lock. This PR adds a test to demonstrate the deadlock and fixes it by holding the lock for shorter scopes.

Fixes#549.

@jkczyz
jkczyz requested a review from TheBlueMattApril 1, 2020 05:31

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, mod the one comment and the commits being "out of order" - we should first fix the issue, then add the test as otherwise we have a state in git history that fails to pass tests.

Comment threadlightning/src/ln/functional_tests.rs Outdated
}

// Alice -> Bob -> Chuck: Route another payment but now Bob waits for Chuck's earlier revoke_and_ack.
let (_, failed_payment_hash) = route_payment(&nodes[0], &[&nodes[1]], 50_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oh lol, guess you dont need three nodes for this test. Note that this is a completely separate payment from the next one - we disambiguate by HTLCSource, not the payment_hash (as otherwise there are a number of privacy and practical funds issues). The payment_failed at the end is the indicator - its saying that a payment nodes[1] tried to send failed, not that it should fail back the HTLC to nodes[0].

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Groking: what is "the next one" referring to?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

That was a reference to the manual send_payment call two lines down.

Ah, I see. This was my attempt at simulating nodes[2] not sending revoke_and_ack.

If I only need two nodes, then are you saying I can get rid of the nodes[0] to nodes[1] part entirely (i.e., the places where I'm using route_payment)? In that case, what is being "failed backward"?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yep. The part being "failed back" is purely the event telling us that the payment failed (as the lock in question here is taken before we decide if the HTLCSource is an OutboundRoute which we sent or a PreviousHopData which means someone else sent us an HTLC that we relayed).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, simplified this by only using two nodes in the test.

@TheBlueMatt

TheBlueMatt commented Apr 1, 2020 via email

Copy link
Copy Markdown
Collaborator

TheBlueMattand others added 2 commits April 1, 2020 16:27
This partially reverts 933ae34,
though note that 933ae34 fixed a
similar deadlock while introducing this one.
If we have HTLCs to fail backwards, handle_error!() will call
finish_force_close_channel() which will attempt to lock channel_state
while it is locked at the original caller. Instead, hold the lock for
shorter scopes such that it is not held upon entering handle_error!().
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Jeffrey Czyz <jkczyz@gmail.com>
Upon channel failure, any pending HTLCs in a channel's holding cell must
be failed backward. The added test exercises this behavior and
demonstrates a deadlock triggered within the handle_error!() macro. The
deadlock occurs when the channel_state lock is already held and then
reacquired when finish_force_close_channel() is called.
@jkczyz
jkczyzforce-pushed the 2020-03-handle-error-deadlock branch from 7e25d66 to 3968647CompareApril 1, 2020 23:37
@TheBlueMatt
TheBlueMatt merged commit f0b037c into lightningdevkit:masterApr 2, 2020
Sign up for freeto 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.

Deadlock in handle_error!() on HTLC fail-back

2 participants

@jkczyz@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix deadlock in ChannelManager's handle_error!() - #568

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock
Apr 2, 2020
Merged

Fix deadlock in ChannelManager's handle_error!()#568
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

ChannelManager fails backward any pending HTLCs upon channel failure. A deadlock occurs in such cases since handle_error!() takes a locked channel_state and finish_force_close_channel() attempts to reacquire the lock. This PR adds a test to demonstrate the deadlock and fixes it by holding the lock for shorter scopes.

Fixes#549.

@jkczyz
jkczyz requested a review from TheBlueMattApril 1, 2020 05:31

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, mod the one comment and the commits being "out of order" - we should first fix the issue, then add the test as otherwise we have a state in git history that fails to pass tests.

Comment threadlightning/src/ln/functional_tests.rs Outdated
}

// Alice -> Bob -> Chuck: Route another payment but now Bob waits for Chuck's earlier revoke_and_ack.
let (_, failed_payment_hash) = route_payment(&nodes[0], &[&nodes[1]], 50_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oh lol, guess you dont need three nodes for this test. Note that this is a completely separate payment from the next one - we disambiguate by HTLCSource, not the payment_hash (as otherwise there are a number of privacy and practical funds issues). The payment_failed at the end is the indicator - its saying that a payment nodes[1] tried to send failed, not that it should fail back the HTLC to nodes[0].

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Groking: what is "the next one" referring to?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

That was a reference to the manual send_payment call two lines down.

Ah, I see. This was my attempt at simulating nodes[2] not sending revoke_and_ack.

If I only need two nodes, then are you saying I can get rid of the nodes[0] to nodes[1] part entirely (i.e., the places where I'm using route_payment)? In that case, what is being "failed backward"?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yep. The part being "failed back" is purely the event telling us that the payment failed (as the lock in question here is taken before we decide if the HTLCSource is an OutboundRoute which we sent or a PreviousHopData which means someone else sent us an HTLC that we relayed).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, simplified this by only using two nodes in the test.

@TheBlueMatt

TheBlueMatt commented Apr 1, 2020 via email

Copy link
Copy Markdown
Collaborator

TheBlueMattand others added 2 commits April 1, 2020 16:27
This partially reverts 933ae34,
though note that 933ae34 fixed a
similar deadlock while introducing this one.
If we have HTLCs to fail backwards, handle_error!() will call
finish_force_close_channel() which will attempt to lock channel_state
while it is locked at the original caller. Instead, hold the lock for
shorter scopes such that it is not held upon entering handle_error!().
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Jeffrey Czyz <jkczyz@gmail.com>
Upon channel failure, any pending HTLCs in a channel's holding cell must
be failed backward. The added test exercises this behavior and
demonstrates a deadlock triggered within the handle_error!() macro. The
deadlock occurs when the channel_state lock is already held and then
reacquired when finish_force_close_channel() is called.
@jkczyz
jkczyzforce-pushed the 2020-03-handle-error-deadlock branch from 7e25d66 to 3968647CompareApril 1, 2020 23:37
@TheBlueMatt
TheBlueMatt merged commit f0b037c into lightningdevkit:masterApr 2, 2020
Sign up for freeto 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.

Deadlock in handle_error!() on HTLC fail-back

2 participants

@jkczyz@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix deadlock in ChannelManager's handle_error!() - #568

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock
Apr 2, 2020
Merged

Fix deadlock in ChannelManager's handle_error!()#568
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

ChannelManager fails backward any pending HTLCs upon channel failure. A deadlock occurs in such cases since handle_error!() takes a locked channel_state and finish_force_close_channel() attempts to reacquire the lock. This PR adds a test to demonstrate the deadlock and fixes it by holding the lock for shorter scopes.

Fixes#549.

@jkczyz
jkczyz requested a review from TheBlueMattApril 1, 2020 05:31

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, mod the one comment and the commits being "out of order" - we should first fix the issue, then add the test as otherwise we have a state in git history that fails to pass tests.

Comment threadlightning/src/ln/functional_tests.rs Outdated
}

// Alice -> Bob -> Chuck: Route another payment but now Bob waits for Chuck's earlier revoke_and_ack.
let (_, failed_payment_hash) = route_payment(&nodes[0], &[&nodes[1]], 50_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oh lol, guess you dont need three nodes for this test. Note that this is a completely separate payment from the next one - we disambiguate by HTLCSource, not the payment_hash (as otherwise there are a number of privacy and practical funds issues). The payment_failed at the end is the indicator - its saying that a payment nodes[1] tried to send failed, not that it should fail back the HTLC to nodes[0].

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Groking: what is "the next one" referring to?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

That was a reference to the manual send_payment call two lines down.

Ah, I see. This was my attempt at simulating nodes[2] not sending revoke_and_ack.

If I only need two nodes, then are you saying I can get rid of the nodes[0] to nodes[1] part entirely (i.e., the places where I'm using route_payment)? In that case, what is being "failed backward"?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yep. The part being "failed back" is purely the event telling us that the payment failed (as the lock in question here is taken before we decide if the HTLCSource is an OutboundRoute which we sent or a PreviousHopData which means someone else sent us an HTLC that we relayed).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, simplified this by only using two nodes in the test.

@TheBlueMatt

TheBlueMatt commented Apr 1, 2020 via email

Copy link
Copy Markdown
Collaborator

TheBlueMattand others added 2 commits April 1, 2020 16:27
This partially reverts 933ae34,
though note that 933ae34 fixed a
similar deadlock while introducing this one.
If we have HTLCs to fail backwards, handle_error!() will call
finish_force_close_channel() which will attempt to lock channel_state
while it is locked at the original caller. Instead, hold the lock for
shorter scopes such that it is not held upon entering handle_error!().
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Jeffrey Czyz <jkczyz@gmail.com>
Upon channel failure, any pending HTLCs in a channel's holding cell must
be failed backward. The added test exercises this behavior and
demonstrates a deadlock triggered within the handle_error!() macro. The
deadlock occurs when the channel_state lock is already held and then
reacquired when finish_force_close_channel() is called.
@jkczyz
jkczyzforce-pushed the 2020-03-handle-error-deadlock branch from 7e25d66 to 3968647CompareApril 1, 2020 23:37
@TheBlueMatt
TheBlueMatt merged commit f0b037c into lightningdevkit:masterApr 2, 2020
Sign up for freeto 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.

Deadlock in handle_error!() on HTLC fail-back

2 participants

@jkczyz@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix deadlock in ChannelManager's handle_error!() - #568

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock
Apr 2, 2020
Merged

Fix deadlock in ChannelManager's handle_error!()#568
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

ChannelManager fails backward any pending HTLCs upon channel failure. A deadlock occurs in such cases since handle_error!() takes a locked channel_state and finish_force_close_channel() attempts to reacquire the lock. This PR adds a test to demonstrate the deadlock and fixes it by holding the lock for shorter scopes.

Fixes#549.

@jkczyz
jkczyz requested a review from TheBlueMattApril 1, 2020 05:31

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, mod the one comment and the commits being "out of order" - we should first fix the issue, then add the test as otherwise we have a state in git history that fails to pass tests.

Comment threadlightning/src/ln/functional_tests.rs Outdated
}

// Alice -> Bob -> Chuck: Route another payment but now Bob waits for Chuck's earlier revoke_and_ack.
let (_, failed_payment_hash) = route_payment(&nodes[0], &[&nodes[1]], 50_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oh lol, guess you dont need three nodes for this test. Note that this is a completely separate payment from the next one - we disambiguate by HTLCSource, not the payment_hash (as otherwise there are a number of privacy and practical funds issues). The payment_failed at the end is the indicator - its saying that a payment nodes[1] tried to send failed, not that it should fail back the HTLC to nodes[0].

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Groking: what is "the next one" referring to?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

That was a reference to the manual send_payment call two lines down.

Ah, I see. This was my attempt at simulating nodes[2] not sending revoke_and_ack.

If I only need two nodes, then are you saying I can get rid of the nodes[0] to nodes[1] part entirely (i.e., the places where I'm using route_payment)? In that case, what is being "failed backward"?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yep. The part being "failed back" is purely the event telling us that the payment failed (as the lock in question here is taken before we decide if the HTLCSource is an OutboundRoute which we sent or a PreviousHopData which means someone else sent us an HTLC that we relayed).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, simplified this by only using two nodes in the test.

@TheBlueMatt

TheBlueMatt commented Apr 1, 2020 via email

Copy link
Copy Markdown
Collaborator

TheBlueMattand others added 2 commits April 1, 2020 16:27
This partially reverts 933ae34,
though note that 933ae34 fixed a
similar deadlock while introducing this one.
If we have HTLCs to fail backwards, handle_error!() will call
finish_force_close_channel() which will attempt to lock channel_state
while it is locked at the original caller. Instead, hold the lock for
shorter scopes such that it is not held upon entering handle_error!().
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Jeffrey Czyz <jkczyz@gmail.com>
Upon channel failure, any pending HTLCs in a channel's holding cell must
be failed backward. The added test exercises this behavior and
demonstrates a deadlock triggered within the handle_error!() macro. The
deadlock occurs when the channel_state lock is already held and then
reacquired when finish_force_close_channel() is called.
@jkczyz
jkczyzforce-pushed the 2020-03-handle-error-deadlock branch from 7e25d66 to 3968647CompareApril 1, 2020 23:37
@TheBlueMatt
TheBlueMatt merged commit f0b037c into lightningdevkit:masterApr 2, 2020
Sign up for freeto 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.

Deadlock in handle_error!() on HTLC fail-back

2 participants

@jkczyz@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix deadlock in ChannelManager's handle_error!() - #568

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock
Apr 2, 2020
Merged

Fix deadlock in ChannelManager's handle_error!()#568
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

ChannelManager fails backward any pending HTLCs upon channel failure. A deadlock occurs in such cases since handle_error!() takes a locked channel_state and finish_force_close_channel() attempts to reacquire the lock. This PR adds a test to demonstrate the deadlock and fixes it by holding the lock for shorter scopes.

Fixes#549.

@jkczyz
jkczyz requested a review from TheBlueMattApril 1, 2020 05:31

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, mod the one comment and the commits being "out of order" - we should first fix the issue, then add the test as otherwise we have a state in git history that fails to pass tests.

Comment threadlightning/src/ln/functional_tests.rs Outdated
}

// Alice -> Bob -> Chuck: Route another payment but now Bob waits for Chuck's earlier revoke_and_ack.
let (_, failed_payment_hash) = route_payment(&nodes[0], &[&nodes[1]], 50_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oh lol, guess you dont need three nodes for this test. Note that this is a completely separate payment from the next one - we disambiguate by HTLCSource, not the payment_hash (as otherwise there are a number of privacy and practical funds issues). The payment_failed at the end is the indicator - its saying that a payment nodes[1] tried to send failed, not that it should fail back the HTLC to nodes[0].

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Groking: what is "the next one" referring to?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

That was a reference to the manual send_payment call two lines down.

Ah, I see. This was my attempt at simulating nodes[2] not sending revoke_and_ack.

If I only need two nodes, then are you saying I can get rid of the nodes[0] to nodes[1] part entirely (i.e., the places where I'm using route_payment)? In that case, what is being "failed backward"?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yep. The part being "failed back" is purely the event telling us that the payment failed (as the lock in question here is taken before we decide if the HTLCSource is an OutboundRoute which we sent or a PreviousHopData which means someone else sent us an HTLC that we relayed).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, simplified this by only using two nodes in the test.

@TheBlueMatt

TheBlueMatt commented Apr 1, 2020 via email

Copy link
Copy Markdown
Collaborator

TheBlueMattand others added 2 commits April 1, 2020 16:27
This partially reverts 933ae34,
though note that 933ae34 fixed a
similar deadlock while introducing this one.
If we have HTLCs to fail backwards, handle_error!() will call
finish_force_close_channel() which will attempt to lock channel_state
while it is locked at the original caller. Instead, hold the lock for
shorter scopes such that it is not held upon entering handle_error!().
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Jeffrey Czyz <jkczyz@gmail.com>
Upon channel failure, any pending HTLCs in a channel's holding cell must
be failed backward. The added test exercises this behavior and
demonstrates a deadlock triggered within the handle_error!() macro. The
deadlock occurs when the channel_state lock is already held and then
reacquired when finish_force_close_channel() is called.
@jkczyz
jkczyzforce-pushed the 2020-03-handle-error-deadlock branch from 7e25d66 to 3968647CompareApril 1, 2020 23:37
@TheBlueMatt
TheBlueMatt merged commit f0b037c into lightningdevkit:masterApr 2, 2020
Sign up for freeto 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.

Deadlock in handle_error!() on HTLC fail-back

2 participants

@jkczyz@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix deadlock in ChannelManager's handle_error!() - #568

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock
Apr 2, 2020
Merged

Fix deadlock in ChannelManager's handle_error!()#568
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

ChannelManager fails backward any pending HTLCs upon channel failure. A deadlock occurs in such cases since handle_error!() takes a locked channel_state and finish_force_close_channel() attempts to reacquire the lock. This PR adds a test to demonstrate the deadlock and fixes it by holding the lock for shorter scopes.

Fixes#549.

@jkczyz
jkczyz requested a review from TheBlueMattApril 1, 2020 05:31

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, mod the one comment and the commits being "out of order" - we should first fix the issue, then add the test as otherwise we have a state in git history that fails to pass tests.

Comment threadlightning/src/ln/functional_tests.rs Outdated
}

// Alice -> Bob -> Chuck: Route another payment but now Bob waits for Chuck's earlier revoke_and_ack.
let (_, failed_payment_hash) = route_payment(&nodes[0], &[&nodes[1]], 50_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oh lol, guess you dont need three nodes for this test. Note that this is a completely separate payment from the next one - we disambiguate by HTLCSource, not the payment_hash (as otherwise there are a number of privacy and practical funds issues). The payment_failed at the end is the indicator - its saying that a payment nodes[1] tried to send failed, not that it should fail back the HTLC to nodes[0].

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Groking: what is "the next one" referring to?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

That was a reference to the manual send_payment call two lines down.

Ah, I see. This was my attempt at simulating nodes[2] not sending revoke_and_ack.

If I only need two nodes, then are you saying I can get rid of the nodes[0] to nodes[1] part entirely (i.e., the places where I'm using route_payment)? In that case, what is being "failed backward"?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yep. The part being "failed back" is purely the event telling us that the payment failed (as the lock in question here is taken before we decide if the HTLCSource is an OutboundRoute which we sent or a PreviousHopData which means someone else sent us an HTLC that we relayed).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, simplified this by only using two nodes in the test.

@TheBlueMatt

TheBlueMatt commented Apr 1, 2020 via email

Copy link
Copy Markdown
Collaborator

TheBlueMattand others added 2 commits April 1, 2020 16:27
This partially reverts 933ae34,
though note that 933ae34 fixed a
similar deadlock while introducing this one.
If we have HTLCs to fail backwards, handle_error!() will call
finish_force_close_channel() which will attempt to lock channel_state
while it is locked at the original caller. Instead, hold the lock for
shorter scopes such that it is not held upon entering handle_error!().
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Jeffrey Czyz <jkczyz@gmail.com>
Upon channel failure, any pending HTLCs in a channel's holding cell must
be failed backward. The added test exercises this behavior and
demonstrates a deadlock triggered within the handle_error!() macro. The
deadlock occurs when the channel_state lock is already held and then
reacquired when finish_force_close_channel() is called.
@jkczyz
jkczyzforce-pushed the 2020-03-handle-error-deadlock branch from 7e25d66 to 3968647CompareApril 1, 2020 23:37
@TheBlueMatt
TheBlueMatt merged commit f0b037c into lightningdevkit:masterApr 2, 2020
Sign up for freeto 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.

Deadlock in handle_error!() on HTLC fail-back

2 participants

@jkczyz@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix deadlock in ChannelManager's handle_error!() - #568

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock
Apr 2, 2020
Merged

Fix deadlock in ChannelManager's handle_error!()#568
TheBlueMatt merged 2 commits into
lightningdevkit:masterfrom
jkczyz:2020-03-handle-error-deadlock

Conversation

@jkczyz

Copy link
Copy Markdown
Contributor

ChannelManager fails backward any pending HTLCs upon channel failure. A deadlock occurs in such cases since handle_error!() takes a locked channel_state and finish_force_close_channel() attempts to reacquire the lock. This PR adds a test to demonstrate the deadlock and fixes it by holding the lock for shorter scopes.

Fixes#549.

@jkczyz
jkczyz requested a review from TheBlueMattApril 1, 2020 05:31

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, mod the one comment and the commits being "out of order" - we should first fix the issue, then add the test as otherwise we have a state in git history that fails to pass tests.

Comment threadlightning/src/ln/functional_tests.rs Outdated
}

// Alice -> Bob -> Chuck: Route another payment but now Bob waits for Chuck's earlier revoke_and_ack.
let (_, failed_payment_hash) = route_payment(&nodes[0], &[&nodes[1]], 50_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oh lol, guess you dont need three nodes for this test. Note that this is a completely separate payment from the next one - we disambiguate by HTLCSource, not the payment_hash (as otherwise there are a number of privacy and practical funds issues). The payment_failed at the end is the indicator - its saying that a payment nodes[1] tried to send failed, not that it should fail back the HTLC to nodes[0].

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Groking: what is "the next one" referring to?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

That was a reference to the manual send_payment call two lines down.

Ah, I see. This was my attempt at simulating nodes[2] not sending revoke_and_ack.

If I only need two nodes, then are you saying I can get rid of the nodes[0] to nodes[1] part entirely (i.e., the places where I'm using route_payment)? In that case, what is being "failed backward"?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yep. The part being "failed back" is purely the event telling us that the payment failed (as the lock in question here is taken before we decide if the HTLCSource is an OutboundRoute which we sent or a PreviousHopData which means someone else sent us an HTLC that we relayed).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, simplified this by only using two nodes in the test.

@TheBlueMatt

TheBlueMatt commented Apr 1, 2020 via email

Copy link
Copy Markdown
Collaborator

TheBlueMattand others added 2 commits April 1, 2020 16:27
This partially reverts 933ae34,
though note that 933ae34 fixed a
similar deadlock while introducing this one.
If we have HTLCs to fail backwards, handle_error!() will call
finish_force_close_channel() which will attempt to lock channel_state
while it is locked at the original caller. Instead, hold the lock for
shorter scopes such that it is not held upon entering handle_error!().
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Jeffrey Czyz <jkczyz@gmail.com>
Upon channel failure, any pending HTLCs in a channel's holding cell must
be failed backward. The added test exercises this behavior and
demonstrates a deadlock triggered within the handle_error!() macro. The
deadlock occurs when the channel_state lock is already held and then
reacquired when finish_force_close_channel() is called.
@jkczyz
jkczyzforce-pushed the 2020-03-handle-error-deadlock branch from 7e25d66 to 3968647CompareApril 1, 2020 23:37
@TheBlueMatt
TheBlueMatt merged commit f0b037c into lightningdevkit:masterApr 2, 2020
Sign up for freeto 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.

Deadlock in handle_error!() on HTLC fail-back

2 participants

@jkczyz@TheBlueMatt