)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a6070f00a375b9604d4fa01713cef0f6a360a690","unresolved":true,"context_lines":[{"line_number":4,"context_line":"Commit:     Nathan Nguyen \u003cnguyennathan1502@gmail.com\u003e"},{"line_number":5,"context_line":"CommitDate: 2025-04-03 21:09:33 -0400"},{"line_number":6,"context_line":""},{"line_number":7,"context_line":"Implement heartbeat response for COPY request"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"Change-Id: Iaf4355f2e0edbd6dde226231f3d7a61a5e53ecfa"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":8,"id":"6680c211_e8409c36","line":7,"updated":"2025-04-28 22:52:28.000000000","message":"Probably time to flesh out the commit message a little:\n\n- How does one use this? _Why_ might a client want to use it?\n- What changes when it\u0027s on?\n- Is there a bug open about it? You can add a `Closes-Bug: \u003claunchpad #\u003e` to link it -- this not only helps us find it later; it\u0027ll also automatically close the bug when this merges.","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"12900d8d1001404d3adc1bcac48941431d04a978","unresolved":false,"context_lines":[{"line_number":4,"context_line":"Commit:     Nathan Nguyen \u003cnguyennathan1502@gmail.com\u003e"},{"line_number":5,"context_line":"CommitDate: 2025-04-03 21:09:33 -0400"},{"line_number":6,"context_line":""},{"line_number":7,"context_line":"Implement heartbeat response for COPY request"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"Change-Id: Iaf4355f2e0edbd6dde226231f3d7a61a5e53ecfa"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":8,"id":"3bf2ce04_112d603a","line":7,"in_reply_to":"6680c211_e8409c36","updated":"2025-05-01 01:16:41.000000000","message":"Done","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"a03a61e475f5b303ef3b51263c920cec1bafc5f6","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":1,"id":"23d7e255_0884c81f","updated":"2025-02-05 04:54:10.000000000","message":"The code is still WIP","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"b617f584ad2b1766c21b56bc14ecff6794de1913","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"c5aec1a2_2136b84e","in_reply_to":"23d7e255_0884c81f","updated":"2025-02-06 04:02:09.000000000","message":"Done","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"6e350273c8c30ffa33bcc7555fe2ef873bb54271","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"aa39afd6_5199ee7b","updated":"2025-02-06 20:44:58.000000000","message":"We should start thinking about how to test this!","commit_id":"64d2920b601604cfc43c3f80e1dbbd1f1d5a99a4"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"8b674cab03954036c24da9f79dff56d4982f520b","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"680ea80d_01705e90","updated":"2025-03-24 02:35:48.000000000","message":"Looking awesome Nathan!\n\nWhen comparing this to the SLO heartbeat, it returns the final resulting status like:\n```\nvagrant@saio3:~$ curl -i -H \"X-Auth-Token: $OS_AUTH_TOKEN\" \"$OS_STORAGE_URL/cont/slo?multipart-manifest\u003dput\u0026heartbeat\u003don\" -X PUT -T slo.txt \nHTTP/1.1 100 Continue\n\nHTTP/1.1 202 Accepted\nContent-Type: text/plain\nX-Trans-Id: tx35d51a17bc854efe917c9-0067e0c146\nX-Openstack-Request-Id: tx35d51a17bc854efe917c9-0067e0c146\nDate: Mon, 24 Mar 2025 02:19:50 GMT\nTransfer-Encoding: chunked\n\n \n\nEtag: \"e3ad15030640911930b3f008178fb0f9\"\nLast Modified: Mon, 24 Mar 2025 02:19:51 GMT\nResponse Body: \nResponse Status: 201 Created\nErrors:\n```\n\nWe probably don\u0027t need that much data, becuase there isn\u0027t a bunch expected segment errors or anything. But I wonder if we should use a simlar form, maybe the response status at the least?:\n\n```\nResponse Status: 201 Created\n```\n\nOr do we move the builks get_response_body to somewhere SLO, copy and bulk can use it. Then we can at least have response consistency from all the heartbeats\u003don\u0027s in the code base","commit_id":"159804c6f41ac81945d2361bd14935967ed1499c"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a6070f00a375b9604d4fa01713cef0f6a360a690","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":8,"id":"37b9867a_3943ef60","updated":"2025-04-28 22:52:28.000000000","message":"I threw some ideas in https://paste.opendev.org/show/bovgsz3GTesr7PZjYbNm/ -- mainly: reduce how much is going on deep inside the wait-loop, make sure the response body makes it out (especially useful on errors), and make unhandled exceptions more like non-2xx responses.","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"0f0604d7ed89c3b444de99c8ce66d17e3f4fd279","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":11,"id":"478412a7_643401af","updated":"2025-05-05 21:10:55.000000000","message":"LGTM -- I\u0027ll see if I can get @matt@oliver.net.au to take another look.","commit_id":"aedbaac1e71fff7ae73738ffb1b6995c698bb0eb"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"6456ec9fc06e18b5161a07beb46e25f7d4870d8f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":14,"id":"4b1132b5_cdeb2338","updated":"2025-05-08 10:26:03.000000000","message":"This is really great and I think just about ready to merge.. we did talk about a func test. But also happy for that to be a follow up.\n\nOnly is `from swift.common.request_helpers import` happening twice in one file. And maybe a function name NIT (nitpick) which I don\u0027t think is a blocker in my opinon, but could be nice.\n\nSo I think if you fix the import at least we can probably merge it.","commit_id":"d0d177c933a73ba9cd45f89ea00b6e63a2e200ed"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"c425514780783ddbd13f5e7fcf3ddb624c399139","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":15,"id":"84de9534_29e9235f","updated":"2025-05-08 16:29:27.000000000","message":"I think it looks good, I\u0027ll write the func tests in a follow-up patch. Thank you","commit_id":"9bd6ffe879e314189181e7d9b8e518c24efe8c81"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"1d452676059f842cb0e1808fab25269ad309a481","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":16,"id":"ea542bfe_78e971bb","updated":"2025-05-08 18:26:09.000000000","message":"Fixed some typo and a few tests after renaming function in request_helpers","commit_id":"9a445503e934eaedfdceb3afc8908a3c6688d51c"}],"swift/common/middleware/bulk.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a6070f00a375b9604d4fa01713cef0f6a360a690","unresolved":true,"context_lines":[{"line_number":197,"context_line":"# import json"},{"line_number":198,"context_line":"from swift.common.utils.get_response_body import get_response_body"},{"line_number":199,"context_line":"import tarfile"},{"line_number":200,"context_line":"# from xml.sax.saxutils import escape  # nosec B406"},{"line_number":201,"context_line":"from time import time"},{"line_number":202,"context_line":"from eventlet import sleep"},{"line_number":203,"context_line":"import zlib"}],"source_content_type":"text/x-python","patch_set":8,"id":"dd3a4f49_3012e8e1","line":200,"updated":"2025-04-28 22:52:28.000000000","message":"We can just drop these; no need to preserve as comments.","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"12900d8d1001404d3adc1bcac48941431d04a978","unresolved":false,"context_lines":[{"line_number":197,"context_line":"# import json"},{"line_number":198,"context_line":"from swift.common.utils.get_response_body import get_response_body"},{"line_number":199,"context_line":"import tarfile"},{"line_number":200,"context_line":"# from xml.sax.saxutils import escape  # nosec B406"},{"line_number":201,"context_line":"from time import time"},{"line_number":202,"context_line":"from eventlet import sleep"},{"line_number":203,"context_line":"import zlib"}],"source_content_type":"text/x-python","patch_set":8,"id":"37d69f63_806a839f","line":200,"in_reply_to":"dd3a4f49_3012e8e1","updated":"2025-05-01 01:16:41.000000000","message":"Done","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"}],"swift/common/middleware/copy.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"d6c82d7d919cb5cacaf262d464de747e53554630","unresolved":true,"context_lines":[{"line_number":195,"context_line":"    def send_put_req(self, req, additional_resp_headers, start_response):"},{"line_number":196,"context_line":"        self.logger.debug(\u0027Sending put req: %.3f\u0027, get_time() - self.start)"},{"line_number":197,"context_line":"        heartbeat \u003d config_true_value(req.params.get(\u0027heartbeat\u0027))"},{"line_number":198,"context_line":"        ACCEPTABLE_FORMATS \u003d [\u0027text/plain\u0027, \u0027application/json\u0027, \u0027application/xml\u0027, \u0027text/xml\u0027]"},{"line_number":199,"context_line":""},{"line_number":200,"context_line":"        try:"},{"line_number":201,"context_line":"            out_content_type \u003d req.accept.best_match(ACCEPTABLE_FORMATS)"}],"source_content_type":"text/x-python","patch_set":1,"id":"2a25596f_b1dacdf5","line":198,"range":{"start_line":198,"start_character":64,"end_line":198,"end_character":93},"updated":"2025-02-05 19:21:53.000000000","message":"I\u0027d shy away from adding any new API responses featuring XML.","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"b617f584ad2b1766c21b56bc14ecff6794de1913","unresolved":false,"context_lines":[{"line_number":195,"context_line":"    def send_put_req(self, req, additional_resp_headers, start_response):"},{"line_number":196,"context_line":"        self.logger.debug(\u0027Sending put req: %.3f\u0027, get_time() - self.start)"},{"line_number":197,"context_line":"        heartbeat \u003d config_true_value(req.params.get(\u0027heartbeat\u0027))"},{"line_number":198,"context_line":"        ACCEPTABLE_FORMATS \u003d [\u0027text/plain\u0027, \u0027application/json\u0027, \u0027application/xml\u0027, \u0027text/xml\u0027]"},{"line_number":199,"context_line":""},{"line_number":200,"context_line":"        try:"},{"line_number":201,"context_line":"            out_content_type \u003d req.accept.best_match(ACCEPTABLE_FORMATS)"}],"source_content_type":"text/x-python","patch_set":1,"id":"3b6f7858_524c0070","line":198,"range":{"start_line":198,"start_character":64,"end_line":198,"end_character":93},"in_reply_to":"2a25596f_b1dacdf5","updated":"2025-02-06 04:02:09.000000000","message":"Done","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"d6c82d7d919cb5cacaf262d464de747e53554630","unresolved":true,"context_lines":[{"line_number":203,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":204,"context_line":"        if not out_content_type:"},{"line_number":205,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":206,"context_line":"        app_resp \u003d self._app_call(req.environ)"},{"line_number":207,"context_line":""},{"line_number":208,"context_line":"        if heartbeat:"},{"line_number":209,"context_line":"            self.logger.info(\u0027Heartbeat enabled with frequency %.1f seconds\u0027,"}],"source_content_type":"text/x-python","patch_set":1,"id":"3674d3ea_4734e61f","line":206,"updated":"2025-02-05 19:21:53.000000000","message":"I think this should only be in the `if not heartbeat` path.","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"b617f584ad2b1766c21b56bc14ecff6794de1913","unresolved":false,"context_lines":[{"line_number":203,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":204,"context_line":"        if not out_content_type:"},{"line_number":205,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":206,"context_line":"        app_resp \u003d self._app_call(req.environ)"},{"line_number":207,"context_line":""},{"line_number":208,"context_line":"        if heartbeat:"},{"line_number":209,"context_line":"            self.logger.info(\u0027Heartbeat enabled with frequency %.1f seconds\u0027,"}],"source_content_type":"text/x-python","patch_set":1,"id":"df954cc2_753149d2","line":206,"in_reply_to":"3674d3ea_4734e61f","updated":"2025-02-06 04:02:09.000000000","message":"Done","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"d6c82d7d919cb5cacaf262d464de747e53554630","unresolved":true,"context_lines":[{"line_number":223,"context_line":"                    except eventlet.Timeout:"},{"line_number":224,"context_line":"                        self.logger.debug(\u0027Sending heartbeat at %.3f\u0027, get_time())"},{"line_number":225,"context_line":"                        yield b\u0027 \u0027"},{"line_number":226,"context_line":"                return app_resp"},{"line_number":227,"context_line":"            return resp_iter()"},{"line_number":228,"context_line":"        else:"},{"line_number":229,"context_line":"            self._adjust_put_response(req, additional_resp_headers)"}],"source_content_type":"text/x-python","patch_set":1,"id":"327703f7_6c4c4dae","line":226,"updated":"2025-02-05 19:21:53.000000000","message":"Shouldn\u0027t we do something to look at the response and build up a reasonable remainder of the body?","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"b617f584ad2b1766c21b56bc14ecff6794de1913","unresolved":false,"context_lines":[{"line_number":223,"context_line":"                    except eventlet.Timeout:"},{"line_number":224,"context_line":"                        self.logger.debug(\u0027Sending heartbeat at %.3f\u0027, get_time())"},{"line_number":225,"context_line":"                        yield b\u0027 \u0027"},{"line_number":226,"context_line":"                return app_resp"},{"line_number":227,"context_line":"            return resp_iter()"},{"line_number":228,"context_line":"        else:"},{"line_number":229,"context_line":"            self._adjust_put_response(req, additional_resp_headers)"}],"source_content_type":"text/x-python","patch_set":1,"id":"2461a667_a89b226b","line":226,"in_reply_to":"327703f7_6c4c4dae","updated":"2025-02-06 04:02:09.000000000","message":"Done","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"a03a61e475f5b303ef3b51263c920cec1bafc5f6","unresolved":true,"context_lines":[{"line_number":203,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":204,"context_line":"        if not out_content_type:"},{"line_number":205,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":206,"context_line":"        app_resp \u003d self._app_call(req.environ)"},{"line_number":207,"context_line":""},{"line_number":208,"context_line":"        if heartbeat:"},{"line_number":209,"context_line":"            self.logger.info(\u0027Heartbeat enabled with frequency %.1f seconds\u0027,"},{"line_number":210,"context_line":"                            self.yield_frequency)"},{"line_number":211,"context_line":"            req.environ[\u0027eventlet.minimum_write_chunk_size\u0027] \u003d 0"},{"line_number":212,"context_line":"            gt \u003d eventlet.spawn(self._app_call, req.environ)"},{"line_number":213,"context_line":"            self._adjust_put_response(req, additional_resp_headers)"},{"line_number":214,"context_line":"            start_response(\u0027202 Accepted\u0027, ["},{"line_number":215,"context_line":"                (\u0027Content-Type\u0027, out_content_type),"},{"line_number":216,"context_line":"            ])"},{"line_number":217,"context_line":""},{"line_number":218,"context_line":"            def resp_iter():"},{"line_number":219,"context_line":"                while not gt.dead:"},{"line_number":220,"context_line":"                    try:"},{"line_number":221,"context_line":"                        with eventlet.Timeout(self.yield_frequency):"},{"line_number":222,"context_line":"                            app_resp \u003d gt.wait()"},{"line_number":223,"context_line":"                    except eventlet.Timeout:"},{"line_number":224,"context_line":"                        self.logger.debug(\u0027Sending heartbeat at %.3f\u0027, get_time())"},{"line_number":225,"context_line":"                        yield b\u0027 \u0027"},{"line_number":226,"context_line":"                return app_resp"},{"line_number":227,"context_line":"            return resp_iter()"},{"line_number":228,"context_line":"        else:"},{"line_number":229,"context_line":"            self._adjust_put_response(req, additional_resp_headers)"},{"line_number":230,"context_line":"            start_response(self._response_status,"}],"source_content_type":"text/x-python","patch_set":1,"id":"01229322_bf52f666","line":227,"range":{"start_line":206,"start_character":8,"end_line":227,"end_character":30},"updated":"2025-02-05 04:54:10.000000000","message":"When logging, I don\u0027t see the heartbeat being sent every yield_frequency(1 second in this case). I have tried many other ways but still could not figure out why the greenthread won\u0027t send the heartbeat to the client. The COPY request works fine without error. I\u0027ll delete all the logging before the final version of the patch.\n\nThank you in advance.","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":597,"name":"Pete Zaitcev","email":"zaitcev@kotori.zaitcev.us","username":"zaitcev"},"change_message_id":"49dfab6d430ac67ce6a98c6d28e0d49f6e131424","unresolved":true,"context_lines":[{"line_number":203,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":204,"context_line":"        if not out_content_type:"},{"line_number":205,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":206,"context_line":"        app_resp \u003d self._app_call(req.environ)"},{"line_number":207,"context_line":""},{"line_number":208,"context_line":"        if heartbeat:"},{"line_number":209,"context_line":"            self.logger.info(\u0027Heartbeat enabled with frequency %.1f seconds\u0027,"},{"line_number":210,"context_line":"                            self.yield_frequency)"},{"line_number":211,"context_line":"            req.environ[\u0027eventlet.minimum_write_chunk_size\u0027] \u003d 0"},{"line_number":212,"context_line":"            gt \u003d eventlet.spawn(self._app_call, req.environ)"},{"line_number":213,"context_line":"            self._adjust_put_response(req, additional_resp_headers)"},{"line_number":214,"context_line":"            start_response(\u0027202 Accepted\u0027, ["},{"line_number":215,"context_line":"                (\u0027Content-Type\u0027, out_content_type),"},{"line_number":216,"context_line":"            ])"},{"line_number":217,"context_line":""},{"line_number":218,"context_line":"            def resp_iter():"},{"line_number":219,"context_line":"                while not gt.dead:"},{"line_number":220,"context_line":"                    try:"},{"line_number":221,"context_line":"                        with eventlet.Timeout(self.yield_frequency):"},{"line_number":222,"context_line":"                            app_resp \u003d gt.wait()"},{"line_number":223,"context_line":"                    except eventlet.Timeout:"},{"line_number":224,"context_line":"                        self.logger.debug(\u0027Sending heartbeat at %.3f\u0027, get_time())"},{"line_number":225,"context_line":"                        yield b\u0027 \u0027"},{"line_number":226,"context_line":"                return app_resp"},{"line_number":227,"context_line":"            return resp_iter()"},{"line_number":228,"context_line":"        else:"},{"line_number":229,"context_line":"            self._adjust_put_response(req, additional_resp_headers)"},{"line_number":230,"context_line":"            start_response(self._response_status,"}],"source_content_type":"text/x-python","patch_set":1,"id":"24bd4ca1_37b28421","line":227,"range":{"start_line":206,"start_character":8,"end_line":227,"end_character":30},"in_reply_to":"01229322_bf52f666","updated":"2025-02-05 14:59:18.000000000","message":"Did you give a thought to not spawning a thread in the first place? It seems to me that to send these responses between chunks copied would be more honest. This way the client could know that the copy is actually ongoing, and it\u0027s not just the hearbeat thread (although, it probably could deliver current percentage).","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"d6c82d7d919cb5cacaf262d464de747e53554630","unresolved":true,"context_lines":[{"line_number":203,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":204,"context_line":"        if not out_content_type:"},{"line_number":205,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":206,"context_line":"        app_resp \u003d self._app_call(req.environ)"},{"line_number":207,"context_line":""},{"line_number":208,"context_line":"        if heartbeat:"},{"line_number":209,"context_line":"            self.logger.info(\u0027Heartbeat enabled with frequency %.1f seconds\u0027,"},{"line_number":210,"context_line":"                            self.yield_frequency)"},{"line_number":211,"context_line":"            req.environ[\u0027eventlet.minimum_write_chunk_size\u0027] \u003d 0"},{"line_number":212,"context_line":"            gt \u003d eventlet.spawn(self._app_call, req.environ)"},{"line_number":213,"context_line":"            self._adjust_put_response(req, additional_resp_headers)"},{"line_number":214,"context_line":"            start_response(\u0027202 Accepted\u0027, ["},{"line_number":215,"context_line":"                (\u0027Content-Type\u0027, out_content_type),"},{"line_number":216,"context_line":"            ])"},{"line_number":217,"context_line":""},{"line_number":218,"context_line":"            def resp_iter():"},{"line_number":219,"context_line":"                while not gt.dead:"},{"line_number":220,"context_line":"                    try:"},{"line_number":221,"context_line":"                        with eventlet.Timeout(self.yield_frequency):"},{"line_number":222,"context_line":"                            app_resp \u003d gt.wait()"},{"line_number":223,"context_line":"                    except eventlet.Timeout:"},{"line_number":224,"context_line":"                        self.logger.debug(\u0027Sending heartbeat at %.3f\u0027, get_time())"},{"line_number":225,"context_line":"                        yield b\u0027 \u0027"},{"line_number":226,"context_line":"                return app_resp"},{"line_number":227,"context_line":"            return resp_iter()"},{"line_number":228,"context_line":"        else:"},{"line_number":229,"context_line":"            self._adjust_put_response(req, additional_resp_headers)"},{"line_number":230,"context_line":"            start_response(self._response_status,"}],"source_content_type":"text/x-python","patch_set":1,"id":"450c2f24_62393641","line":227,"range":{"start_line":206,"start_character":8,"end_line":227,"end_character":30},"in_reply_to":"24bd4ca1_37b28421","updated":"2025-02-05 19:21:53.000000000","message":"FWIW, this was my recommendation from https://review.opendev.org/c/openstack/swift/+/937082\n\nWe could try to get it out in between chunks, but then we\u0027d have to extend/subclass the `FileLikeIter` we use down around L461 and I think the plumbing would be tricky. We want to push bytes out to the client, but we can\u0027t really push bytes down to the proxy -- we can only hand off control so it can only pull them out of us. Maybe that sort of thing would get easier with something like ASGI?","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"b617f584ad2b1766c21b56bc14ecff6794de1913","unresolved":false,"context_lines":[{"line_number":203,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":204,"context_line":"        if not out_content_type:"},{"line_number":205,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":206,"context_line":"        app_resp \u003d self._app_call(req.environ)"},{"line_number":207,"context_line":""},{"line_number":208,"context_line":"        if heartbeat:"},{"line_number":209,"context_line":"            self.logger.info(\u0027Heartbeat enabled with frequency %.1f seconds\u0027,"},{"line_number":210,"context_line":"                            self.yield_frequency)"},{"line_number":211,"context_line":"            req.environ[\u0027eventlet.minimum_write_chunk_size\u0027] \u003d 0"},{"line_number":212,"context_line":"            gt \u003d eventlet.spawn(self._app_call, req.environ)"},{"line_number":213,"context_line":"            self._adjust_put_response(req, additional_resp_headers)"},{"line_number":214,"context_line":"            start_response(\u0027202 Accepted\u0027, ["},{"line_number":215,"context_line":"                (\u0027Content-Type\u0027, out_content_type),"},{"line_number":216,"context_line":"            ])"},{"line_number":217,"context_line":""},{"line_number":218,"context_line":"            def resp_iter():"},{"line_number":219,"context_line":"                while not gt.dead:"},{"line_number":220,"context_line":"                    try:"},{"line_number":221,"context_line":"                        with eventlet.Timeout(self.yield_frequency):"},{"line_number":222,"context_line":"                            app_resp \u003d gt.wait()"},{"line_number":223,"context_line":"                    except eventlet.Timeout:"},{"line_number":224,"context_line":"                        self.logger.debug(\u0027Sending heartbeat at %.3f\u0027, get_time())"},{"line_number":225,"context_line":"                        yield b\u0027 \u0027"},{"line_number":226,"context_line":"                return app_resp"},{"line_number":227,"context_line":"            return resp_iter()"},{"line_number":228,"context_line":"        else:"},{"line_number":229,"context_line":"            self._adjust_put_response(req, additional_resp_headers)"},{"line_number":230,"context_line":"            start_response(self._response_status,"}],"source_content_type":"text/x-python","patch_set":1,"id":"3b727caf_903d8d9c","line":227,"range":{"start_line":206,"start_character":8,"end_line":227,"end_character":30},"in_reply_to":"450c2f24_62393641","updated":"2025-02-06 04:02:09.000000000","message":"Done","commit_id":"34674738af1942fa313ae2f7df9eb17e2bd6997a"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"6e350273c8c30ffa33bcc7555fe2ef873bb54271","unresolved":true,"context_lines":[{"line_number":200,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":201,"context_line":""},{"line_number":202,"context_line":"        if heartbeat:"},{"line_number":203,"context_line":"            req.environ[\u0027eventlet.minimum_write_chunk_size\u0027] \u003d 0"},{"line_number":204,"context_line":"            gt \u003d eventlet.spawn(self._app_call,"},{"line_number":205,"context_line":"                                req.environ)"},{"line_number":206,"context_line":"            start_response(\u0027202 Accepted\u0027,"}],"source_content_type":"text/x-python","patch_set":2,"id":"f9a0f841_200e8bf1","line":203,"updated":"2025-02-06 20:44:58.000000000","message":"So this sets it for the *subrequest*, but not the original *client request* -- as a result, the client won\u0027t see the heartbeats until we get to the default `minimum_write_chunk_size`, [which is like 4k](https://github.com/eventlet/eventlet/blob/master/eventlet/wsgi.py#L23) (though we maybe override that somewhere).\n\nThe copy happens down around L391; despite saying `environ\u003dreq.environ` we\u0027re not actually using the same request environment, but [copying it to a new one](https://github.com/openstack/swift/blob/2.34.0/swift/common/swob.py#L956-L971).\n\nYou might find curl\u0027s [`-N` option](https://curl.se/docs/manpage.html#-N) useful while testing.","commit_id":"64d2920b601604cfc43c3f80e1dbbd1f1d5a99a4"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"12900d8d1001404d3adc1bcac48941431d04a978","unresolved":false,"context_lines":[{"line_number":200,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":201,"context_line":""},{"line_number":202,"context_line":"        if heartbeat:"},{"line_number":203,"context_line":"            req.environ[\u0027eventlet.minimum_write_chunk_size\u0027] \u003d 0"},{"line_number":204,"context_line":"            gt \u003d eventlet.spawn(self._app_call,"},{"line_number":205,"context_line":"                                req.environ)"},{"line_number":206,"context_line":"            start_response(\u0027202 Accepted\u0027,"}],"source_content_type":"text/x-python","patch_set":2,"id":"0a375b10_c3572d50","line":203,"in_reply_to":"5c8604f8_09f91a45","updated":"2025-05-01 01:16:41.000000000","message":"Done","commit_id":"64d2920b601604cfc43c3f80e1dbbd1f1d5a99a4"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"5931c9ea171e80ea2eda5600b652c7f910463ed6","unresolved":true,"context_lines":[{"line_number":200,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":201,"context_line":""},{"line_number":202,"context_line":"        if heartbeat:"},{"line_number":203,"context_line":"            req.environ[\u0027eventlet.minimum_write_chunk_size\u0027] \u003d 0"},{"line_number":204,"context_line":"            gt \u003d eventlet.spawn(self._app_call,"},{"line_number":205,"context_line":"                                req.environ)"},{"line_number":206,"context_line":"            start_response(\u0027202 Accepted\u0027,"}],"source_content_type":"text/x-python","patch_set":2,"id":"5c8604f8_09f91a45","line":203,"in_reply_to":"f9a0f841_200e8bf1","updated":"2025-02-07 05:43:47.000000000","message":"I wonder if this is possible in my fix where I yield a heartbeat right away as well as set the minimum_write_chunk_size to the client request before making subrequests (not sure if I\u0027m doing it the right way). \n\nThe logs does show an initial heartbeat being yielded. And the response status to be 201 Created after yield self._response_status.encode(\u0027utf-8\u0027). \n\nThank you.","commit_id":"64d2920b601604cfc43c3f80e1dbbd1f1d5a99a4"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"6e350273c8c30ffa33bcc7555fe2ef873bb54271","unresolved":true,"context_lines":[{"line_number":211,"context_line":"                    while not gt.dead:"},{"line_number":212,"context_line":"                        try:"},{"line_number":213,"context_line":"                            with eventlet.Timeout(self.yield_frequency, True):"},{"line_number":214,"context_line":"                                gt.wait()"},{"line_number":215,"context_line":"                        except eventlet.Timeout:"},{"line_number":216,"context_line":"                            yield b\u0027 \u0027"},{"line_number":217,"context_line":"                finally:"}],"source_content_type":"text/x-python","patch_set":2,"id":"b7d70258_c65f56fa","line":214,"updated":"2025-02-06 20:44:58.000000000","message":"We ought to take a look at the response and pass back the status (and probably the body) to the client once the request completes. Otherwise the only way they can tell whether it succeeded or failed (say, because of network failures mid-stream) is by doing a HEAD on the destination afterwards.","commit_id":"64d2920b601604cfc43c3f80e1dbbd1f1d5a99a4"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"5931c9ea171e80ea2eda5600b652c7f910463ed6","unresolved":true,"context_lines":[{"line_number":211,"context_line":"                    while not gt.dead:"},{"line_number":212,"context_line":"                        try:"},{"line_number":213,"context_line":"                            with eventlet.Timeout(self.yield_frequency, True):"},{"line_number":214,"context_line":"                                gt.wait()"},{"line_number":215,"context_line":"                        except eventlet.Timeout:"},{"line_number":216,"context_line":"                            yield b\u0027 \u0027"},{"line_number":217,"context_line":"                finally:"}],"source_content_type":"text/x-python","patch_set":2,"id":"f07dc4fd_6c8fb4fa","line":214,"in_reply_to":"b7d70258_c65f56fa","updated":"2025-02-07 05:43:47.000000000","message":"I\u0027m not sure if we send back the response status (success/error) with yield or we need to start_response for it to actually send back or if there is a standard way for us to do this.","commit_id":"64d2920b601604cfc43c3f80e1dbbd1f1d5a99a4"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"12900d8d1001404d3adc1bcac48941431d04a978","unresolved":false,"context_lines":[{"line_number":211,"context_line":"                    while not gt.dead:"},{"line_number":212,"context_line":"                        try:"},{"line_number":213,"context_line":"                            with eventlet.Timeout(self.yield_frequency, True):"},{"line_number":214,"context_line":"                                gt.wait()"},{"line_number":215,"context_line":"                        except eventlet.Timeout:"},{"line_number":216,"context_line":"                            yield b\u0027 \u0027"},{"line_number":217,"context_line":"                finally:"}],"source_content_type":"text/x-python","patch_set":2,"id":"1f021302_60ec3164","line":214,"in_reply_to":"cdc5e190_c2267606","updated":"2025-05-01 01:16:41.000000000","message":"Done","commit_id":"64d2920b601604cfc43c3f80e1dbbd1f1d5a99a4"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"881d1ab0eda6fa5b27aaace0b48bdb4ffc8efe65","unresolved":true,"context_lines":[{"line_number":211,"context_line":"                    while not gt.dead:"},{"line_number":212,"context_line":"                        try:"},{"line_number":213,"context_line":"                            with eventlet.Timeout(self.yield_frequency, True):"},{"line_number":214,"context_line":"                                gt.wait()"},{"line_number":215,"context_line":"                        except eventlet.Timeout:"},{"line_number":216,"context_line":"                            yield b\u0027 \u0027"},{"line_number":217,"context_line":"                finally:"}],"source_content_type":"text/x-python","patch_set":2,"id":"cdc5e190_c2267606","line":214,"in_reply_to":"d25e83f3_65e86fed","updated":"2025-02-19 21:34:39.000000000","message":"So the test ought to read the whole body, which I\u0027d expect should close out things properly and satisfy our connection-tracker. Key components for the tests will probably be something like\n\n- use a low `yield_frequency` -- 50ms maybe?\n- get the `FakeSwift` call to sleep a bit, or maybe mock out `eventlet.spawn` to add the sleep\n- assert that `eventlet.minimum_write_chunk_size` gets set in the request that the test passes in\n- assert that you get the expected heartbeats before eventually returning some kind of final status\n- cover at least one case where the copy eventually succeeds, and another where it eventually fails\n\n(Generally, I\u0027d try to shy away from sleeps in tests in favor of mocking out `time.time`, but I think it may get hairy to structure the code such that that can work. As long at the test time is low enough it should be tolerable.)","commit_id":"64d2920b601604cfc43c3f80e1dbbd1f1d5a99a4"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"622aa57423932fa53746f991837a0d301c81e8d8","unresolved":true,"context_lines":[{"line_number":211,"context_line":"                    while not gt.dead:"},{"line_number":212,"context_line":"                        try:"},{"line_number":213,"context_line":"                            with eventlet.Timeout(self.yield_frequency, True):"},{"line_number":214,"context_line":"                                gt.wait()"},{"line_number":215,"context_line":"                        except eventlet.Timeout:"},{"line_number":216,"context_line":"                            yield b\u0027 \u0027"},{"line_number":217,"context_line":"                finally:"}],"source_content_type":"text/x-python","patch_set":2,"id":"d25e83f3_65e86fed","line":214,"in_reply_to":"f07dc4fd_6c8fb4fa","updated":"2025-02-18 04:13:56.000000000","message":"I am in the process of writing unit tests for heartbeat\u003don. When I enable heartbeat:\n```\nreq \u003d Request.blank(\n            \u0027/v1/a/c/o?heartbeat\u003don\u0027, method\u003d\u0027COPY\u0027,\n            headers\u003d{\u0027Content-Length\u0027: 0,\n                     \u0027Destination\u0027: \u0027c/o-copy\u0027})\n```\nThe tearDown within test fails as my code is attempting to return 202 and keep the connection open for heartbeat response and cleanup afterwards. \n\n```\nAssertionError: {(\u0027PUT\u0027, \u0027/v1/a/c/o-copy?heartbeat\u003don\u0027): 1} !\u003d {}\n```\nI have been taking a look at other heartbeat tests in test_slo and test_multi_upload and the setup structure looks to be the same. My guess is that my code is wrong in cleaning up and not closing connection properly. I\u0027m open to any suggestion as I\u0027m still stuck on this. Thank you for your help","commit_id":"64d2920b601604cfc43c3f80e1dbbd1f1d5a99a4"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"7a58fe4ffc9fdcb9fe43ae74d53aa3644878e126","unresolved":true,"context_lines":[{"line_number":199,"context_line":"        if not out_content_type:"},{"line_number":200,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":201,"context_line":""},{"line_number":202,"context_line":"        if heartbeat:"},{"line_number":203,"context_line":"            req.environ[\u0027eventlet.minimum_write_chunk_size\u0027] \u003d 0"},{"line_number":204,"context_line":"            gt \u003d eventlet.spawn(self._app_call,"},{"line_number":205,"context_line":"                                req.environ)"},{"line_number":206,"context_line":"            start_response(\u0027202 Accepted\u0027,"},{"line_number":207,"context_line":"                           [(\u0027Content-Type\u0027, out_content_type)])"},{"line_number":208,"context_line":""},{"line_number":209,"context_line":"            def resp_iter():"},{"line_number":210,"context_line":"                try:"},{"line_number":211,"context_line":"                    while not gt.dead:"},{"line_number":212,"context_line":"                        try:"},{"line_number":213,"context_line":"                            with eventlet.Timeout(self.yield_frequency, True):"},{"line_number":214,"context_line":"                                gt.wait()"},{"line_number":215,"context_line":"                        except eventlet.Timeout:"},{"line_number":216,"context_line":"                            yield b\u0027 \u0027"},{"line_number":217,"context_line":"                finally:"},{"line_number":218,"context_line":"                    close_if_possible(gt)"},{"line_number":219,"context_line":"            return resp_iter()"},{"line_number":220,"context_line":""},{"line_number":221,"context_line":"        else:"},{"line_number":222,"context_line":"            app_resp \u003d self._app_call(req.environ)"}],"source_content_type":"text/x-python","patch_set":2,"id":"cde774ee_32d2c93b","line":219,"range":{"start_line":202,"start_character":8,"end_line":219,"end_character":30},"updated":"2025-02-06 04:04:19.000000000","message":"Here is a log where I copied 1GB object with yield_frequency 1 second. https://paste.opendev.org/show/btzYfVqFnSPIjQq8o7Ha/\n\nThank you","commit_id":"64d2920b601604cfc43c3f80e1dbbd1f1d5a99a4"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"5931c9ea171e80ea2eda5600b652c7f910463ed6","unresolved":false,"context_lines":[{"line_number":199,"context_line":"        if not out_content_type:"},{"line_number":200,"context_line":"            out_content_type \u003d \u0027text/plain\u0027"},{"line_number":201,"context_line":""},{"line_number":202,"context_line":"        if heartbeat:"},{"line_number":203,"context_line":"            req.environ[\u0027eventlet.minimum_write_chunk_size\u0027] \u003d 0"},{"line_number":204,"context_line":"            gt \u003d eventlet.spawn(self._app_call,"},{"line_number":205,"context_line":"                                req.environ)"},{"line_number":206,"context_line":"            start_response(\u0027202 Accepted\u0027,"},{"line_number":207,"context_line":"                           [(\u0027Content-Type\u0027, out_content_type)])"},{"line_number":208,"context_line":""},{"line_number":209,"context_line":"            def resp_iter():"},{"line_number":210,"context_line":"                try:"},{"line_number":211,"context_line":"                    while not gt.dead:"},{"line_number":212,"context_line":"                        try:"},{"line_number":213,"context_line":"                            with eventlet.Timeout(self.yield_frequency, True):"},{"line_number":214,"context_line":"                                gt.wait()"},{"line_number":215,"context_line":"                        except eventlet.Timeout:"},{"line_number":216,"context_line":"                            yield b\u0027 \u0027"},{"line_number":217,"context_line":"                finally:"},{"line_number":218,"context_line":"                    close_if_possible(gt)"},{"line_number":219,"context_line":"            return resp_iter()"},{"line_number":220,"context_line":""},{"line_number":221,"context_line":"        else:"},{"line_number":222,"context_line":"            app_resp \u003d self._app_call(req.environ)"}],"source_content_type":"text/x-python","patch_set":2,"id":"35fd2a42_7b7ee30d","line":219,"range":{"start_line":202,"start_character":8,"end_line":219,"end_character":30},"in_reply_to":"cde774ee_32d2c93b","updated":"2025-02-07 05:43:47.000000000","message":"Done","commit_id":"64d2920b601604cfc43c3f80e1dbbd1f1d5a99a4"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a6070f00a375b9604d4fa01713cef0f6a360a690","unresolved":true,"context_lines":[{"line_number":215,"context_line":"                            with eventlet.Timeout(self.yield_frequency):"},{"line_number":216,"context_line":"                                gt.wait()"},{"line_number":217,"context_line":"                                resp_dict \u003d {}"},{"line_number":218,"context_line":"                                resp_dict \u003d {\u0027\\nResponse Status\u0027:"},{"line_number":219,"context_line":"                                             self._response_status}"},{"line_number":220,"context_line":""},{"line_number":221,"context_line":"                                for k, v in additional_resp_headers.items():"}],"source_content_type":"text/x-python","patch_set":8,"id":"fb8c303b_d13f5ae7","line":218,"range":{"start_line":218,"start_character":46,"end_line":218,"end_character":48},"updated":"2025-04-28 22:52:28.000000000","message":"Why the leading newline? Did we mainly want it for the text response? It makes the JSON response a little strange: `{\"\\nResponse Status\": \"201 Created\", ...`\n\nMaybe better as `yield \u0027\\n\u0027 + get_response_body(...)`?","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"12900d8d1001404d3adc1bcac48941431d04a978","unresolved":false,"context_lines":[{"line_number":215,"context_line":"                            with eventlet.Timeout(self.yield_frequency):"},{"line_number":216,"context_line":"                                gt.wait()"},{"line_number":217,"context_line":"                                resp_dict \u003d {}"},{"line_number":218,"context_line":"                                resp_dict \u003d {\u0027\\nResponse Status\u0027:"},{"line_number":219,"context_line":"                                             self._response_status}"},{"line_number":220,"context_line":""},{"line_number":221,"context_line":"                                for k, v in additional_resp_headers.items():"}],"source_content_type":"text/x-python","patch_set":8,"id":"5120d353_66cadd09","line":218,"range":{"start_line":218,"start_character":46,"end_line":218,"end_character":48},"in_reply_to":"fb8c303b_d13f5ae7","updated":"2025-05-01 01:16:41.000000000","message":"Done","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a6070f00a375b9604d4fa01713cef0f6a360a690","unresolved":true,"context_lines":[{"line_number":222,"context_line":"                                    resp_dict[k] \u003d v"},{"line_number":223,"context_line":""},{"line_number":224,"context_line":"                                for k, v in self._response_headers:"},{"line_number":225,"context_line":"                                    resp_dict[k] \u003d v"},{"line_number":226,"context_line":"                                yield get_response_body(out_content_type,"},{"line_number":227,"context_line":"                                                        resp_dict,"},{"line_number":228,"context_line":"                                                        [], \u0027copy\u0027)"}],"source_content_type":"text/x-python","patch_set":8,"id":"7cc3e859_ada0b4b0","line":225,"updated":"2025-04-28 22:52:28.000000000","message":"We probably ought to filter these -- as it is, this can expose sysmeta to clients.","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"12900d8d1001404d3adc1bcac48941431d04a978","unresolved":false,"context_lines":[{"line_number":222,"context_line":"                                    resp_dict[k] \u003d v"},{"line_number":223,"context_line":""},{"line_number":224,"context_line":"                                for k, v in self._response_headers:"},{"line_number":225,"context_line":"                                    resp_dict[k] \u003d v"},{"line_number":226,"context_line":"                                yield get_response_body(out_content_type,"},{"line_number":227,"context_line":"                                                        resp_dict,"},{"line_number":228,"context_line":"                                                        [], \u0027copy\u0027)"}],"source_content_type":"text/x-python","patch_set":8,"id":"e2b629af_03ffd892","line":225,"in_reply_to":"7cc3e859_ada0b4b0","updated":"2025-05-01 01:16:41.000000000","message":"Done","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a6070f00a375b9604d4fa01713cef0f6a360a690","unresolved":true,"context_lines":[{"line_number":225,"context_line":"                                    resp_dict[k] \u003d v"},{"line_number":226,"context_line":"                                yield get_response_body(out_content_type,"},{"line_number":227,"context_line":"                                                        resp_dict,"},{"line_number":228,"context_line":"                                                        [], \u0027copy\u0027)"},{"line_number":229,"context_line":"                                break"},{"line_number":230,"context_line":"                        except eventlet.Timeout:"},{"line_number":231,"context_line":"                            yield b\u0027 \u0027"}],"source_content_type":"text/x-python","patch_set":8,"id":"8212b555_d5f563a8","line":228,"range":{"start_line":228,"start_character":56,"end_line":228,"end_character":58},"updated":"2025-04-28 22:52:28.000000000","message":"It\u0027s a little weird to me that the errors list is always empty, even if the final status was not successful. IDK whether it\u0027d be better to stub in something, like\n```\n                    errors \u003d []\n                    if not is_success(self._get_status_int()):\n                        src_path \u003d additional_resp_headers[\u0027X-Copied-From\u0027]\n                        errors.append((\n                            wsgi_quote(src_path),\n                            self._response_status,\n                        ))\n```\nor update `get_response_body` so if we pass an explicit `None` we drop `Errors` from the return entirely.","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"12900d8d1001404d3adc1bcac48941431d04a978","unresolved":false,"context_lines":[{"line_number":225,"context_line":"                                    resp_dict[k] \u003d v"},{"line_number":226,"context_line":"                                yield get_response_body(out_content_type,"},{"line_number":227,"context_line":"                                                        resp_dict,"},{"line_number":228,"context_line":"                                                        [], \u0027copy\u0027)"},{"line_number":229,"context_line":"                                break"},{"line_number":230,"context_line":"                        except eventlet.Timeout:"},{"line_number":231,"context_line":"                            yield b\u0027 \u0027"}],"source_content_type":"text/x-python","patch_set":8,"id":"95388d97_00a355e2","line":228,"range":{"start_line":228,"start_character":56,"end_line":228,"end_character":58},"in_reply_to":"8212b555_d5f563a8","updated":"2025-05-01 01:16:41.000000000","message":"Done","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a6070f00a375b9604d4fa01713cef0f6a360a690","unresolved":true,"context_lines":[{"line_number":231,"context_line":"                            yield b\u0027 \u0027"},{"line_number":232,"context_line":"                except Exception as e:"},{"line_number":233,"context_line":"                    # Send back the status to the client if error"},{"line_number":234,"context_line":"                    yield str(e).encode(\u0027utf-8\u0027)"},{"line_number":235,"context_line":"                finally:"},{"line_number":236,"context_line":"                    close_if_possible(gt)"},{"line_number":237,"context_line":""}],"source_content_type":"text/x-python","patch_set":8,"id":"73487279_b4830d5e","line":234,"updated":"2025-04-28 22:52:28.000000000","message":"So we only return text on exceptions, even if the client was hoping for a JSON response?\n\nGood news is, this doesn\u0027t trip for things like a bad status; if I break an SLO then try to copy it (for example), I see\n```\n\u003c HTTP/1.1 202 Accepted\n\u003c Content-Type: text/plain\n\u003c X-Trans-Id: tx32367905d1e0412a9153c-00680ffbbb\n\u003c X-Openstack-Request-Id: tx32367905d1e0412a9153c-00680ffbbb\n\u003c Date: Mon, 28 Apr 2025 22:05:47 GMT\n\u003c Transfer-Encoding: chunked\n\u003c \n   \nResponse Status: 499 Client Disconnect\nContent-Length: 89\nContent-Type: text/html; charset\u003dUTF-8\nX-Copied-From: src/myobject\nX-Copied-From-Account: AUTH_test\nX-Copied-From-Last-Modified: Mon, 28 Apr 2025 19:37:48 GMT\nX-Object-Sysmeta-S3Api-Etag: 32686790b1313a7d15194943eefc66a2-7\nX-Object-Sysmeta-S3Api-Upload-Id: MzhkZmFjY2YtYzNjNC00YmRiLTlkMWItM2E3ZjdlNTA0ZmI3\nX-Object-Sysmeta-Slo-Etag: e27f0f128432a6b7164c9daf1164f2a1\nX-Object-Sysmeta-Slo-Size: 104857600\nErrors:\n\n```\n(\"Client Disconnect\" maybe isn\u0027t the best error message, but improving that to something like \"Incomplete read while copying\" is definitely out of scope for this change.)","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"12900d8d1001404d3adc1bcac48941431d04a978","unresolved":false,"context_lines":[{"line_number":231,"context_line":"                            yield b\u0027 \u0027"},{"line_number":232,"context_line":"                except Exception as e:"},{"line_number":233,"context_line":"                    # Send back the status to the client if error"},{"line_number":234,"context_line":"                    yield str(e).encode(\u0027utf-8\u0027)"},{"line_number":235,"context_line":"                finally:"},{"line_number":236,"context_line":"                    close_if_possible(gt)"},{"line_number":237,"context_line":""}],"source_content_type":"text/x-python","patch_set":8,"id":"3e3d4fcd_51938332","line":234,"in_reply_to":"73487279_b4830d5e","updated":"2025-05-01 01:16:41.000000000","message":"Done","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"0f0604d7ed89c3b444de99c8ce66d17e3f4fd279","unresolved":true,"context_lines":[{"line_number":239,"context_line":"                            resp_dict[k] \u003d v"},{"line_number":240,"context_line":""},{"line_number":241,"context_line":"                    for k, v in self._response_headers:"},{"line_number":242,"context_line":"                        if not k.lower().startswith(\u0027x-object-sysmeta-\u0027):"},{"line_number":243,"context_line":"                            resp_dict[k] \u003d v"},{"line_number":244,"context_line":"                    yield get_response_body(out_content_type,"},{"line_number":245,"context_line":"                                            resp_dict,"}],"source_content_type":"text/x-python","patch_set":11,"id":"f79fa856_3c3402ef","line":242,"updated":"2025-05-05 21:10:55.000000000","message":"I\u0027m a little nervous that there might be more headers we want to drop here -- I kind of want to say\n```\nif not k.lower().startswith((\u0027x-object-sysmeta-\u0027, \u0027x-backend-\u0027)):\n```\nBut at the same time, I\u0027m not actually sure *how* `x-backend-` headers might turn up. The [one lead I had](https://github.com/openstack/swift/blob/2.35.0/swift/obj/server.py#L848) didn\u0027t actually pan out -- the proxy turns the 409s into a 202, so I\u0027d get a response like\n```\n\u003c HTTP/1.1 202 Accepted\n\u003c Content-Type: text/plain\n\u003c X-Trans-Id: tx3dd551833d3e42139ad32-00681927e9\n\u003c X-Openstack-Request-Id: tx3dd551833d3e42139ad32-00681927e9\n\u003c Date: Mon, 05 May 2025 21:04:41 GMT\n\u003c Transfer-Encoding: chunked\n\u003c \n Content-Length: 76\nContent-Type: text/html; charset\u003dUTF-8\nResponse Body: \u003chtml\u003e\u003ch1\u003eAccepted\u003c/h1\u003e\u003cp\u003eThe request is accepted for processing.\u003c/p\u003e\u003c/html\u003e\nResponse Status: 202 Accepted\nX-Copied-From: c/o\nX-Copied-From-Account: AUTH_test\nX-Copied-From-Last-Modified: Mon, 05 May 2025 21:03:08 GMT\nErrors:\n\n```","commit_id":"aedbaac1e71fff7ae73738ffb1b6995c698bb0eb"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"9be2153f424f9ec6b0c951c4d3ace9b1e60a9a22","unresolved":false,"context_lines":[{"line_number":239,"context_line":"                            resp_dict[k] \u003d v"},{"line_number":240,"context_line":""},{"line_number":241,"context_line":"                    for k, v in self._response_headers:"},{"line_number":242,"context_line":"                        if not k.lower().startswith(\u0027x-object-sysmeta-\u0027):"},{"line_number":243,"context_line":"                            resp_dict[k] \u003d v"},{"line_number":244,"context_line":"                    yield get_response_body(out_content_type,"},{"line_number":245,"context_line":"                                            resp_dict,"}],"source_content_type":"text/x-python","patch_set":11,"id":"790fcfdf_15389e91","line":242,"in_reply_to":"f79fa856_3c3402ef","updated":"2025-05-06 18:17:38.000000000","message":"I\u0027ve filtered out the x-backend headers. I think I should also write functional tests for the heartbeat too to check there should not be any headers other than the standard ones. I\u0027ll do that in a separate follow-up patch","commit_id":"aedbaac1e71fff7ae73738ffb1b6995c698bb0eb"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"37771de86f8eaf88b143a09e2729fdd5f5c876d6","unresolved":false,"context_lines":[{"line_number":235,"context_line":"                        ))"},{"line_number":236,"context_line":""},{"line_number":237,"context_line":"                    for k, v in additional_resp_headers.items():"},{"line_number":238,"context_line":"                        if not k.lower().startswith((\u0027x-object-sysmeta-\u0027, \u0027x-backend\u0027)):"},{"line_number":239,"context_line":"                            resp_dict[k] \u003d v"},{"line_number":240,"context_line":""},{"line_number":241,"context_line":"                    for k, v in self._response_headers:"}],"source_content_type":"text/x-python","patch_set":13,"id":"81a29385_6186fcfa","line":238,"in_reply_to":"1e803570_15f3908c","updated":"2025-05-07 18:33:08.000000000","message":"\u003e pep8: E501 line too long (88 \u003e 79 characters)\n\nPlease fix.","commit_id":"9e8533d5db4c47d578bc078fb320f3cc534fd87d"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"37771de86f8eaf88b143a09e2729fdd5f5c876d6","unresolved":false,"context_lines":[{"line_number":239,"context_line":"                            resp_dict[k] \u003d v"},{"line_number":240,"context_line":""},{"line_number":241,"context_line":"                    for k, v in self._response_headers:"},{"line_number":242,"context_line":"                        if not k.lower().startswith(\u0027x-object-sysmeta-\u0027, \u0027x-backend\u0027):"},{"line_number":243,"context_line":"                            resp_dict[k] \u003d v"},{"line_number":244,"context_line":"                    yield get_response_body(out_content_type,"},{"line_number":245,"context_line":"                                            resp_dict,"}],"source_content_type":"text/x-python","patch_set":13,"id":"fc25b2af_7399323c","line":242,"in_reply_to":"72835c3e_33f0d897","updated":"2025-05-07 18:33:08.000000000","message":"\u003e pep8: E501 line too long (86 \u003e 79 characters)\n\nPlease fix.","commit_id":"9e8533d5db4c47d578bc078fb320f3cc534fd87d"}],"swift/common/middleware/slo.py":[{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"6456ec9fc06e18b5161a07beb46e25f7d4870d8f","unresolved":true,"context_lines":[{"line_number":347,"context_line":"import time"},{"line_number":348,"context_line":""},{"line_number":349,"context_line":"from swift.cli.container_deleter import make_delete_jobs"},{"line_number":350,"context_line":"from swift.common.request_helpers import get_response_body"},{"line_number":351,"context_line":"from swift.common.header_key_dict import HeaderKeyDict"},{"line_number":352,"context_line":"from swift.common.exceptions import ListingIterError, SegmentError"},{"line_number":353,"context_line":"from swift.common.middleware.listing_formats import \\"}],"source_content_type":"text/x-python","patch_set":14,"id":"2ab5a618_0138b1c5","line":350,"range":{"start_line":350,"start_character":5,"end_line":350,"end_character":33},"updated":"2025-05-08 10:26:03.000000000","message":"We\u0027re already importing from request_helpers on line 367.. so we should just add get_response_bocy to that list.","commit_id":"d0d177c933a73ba9cd45f89ea00b6e63a2e200ed"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"c425514780783ddbd13f5e7fcf3ddb624c399139","unresolved":false,"context_lines":[{"line_number":347,"context_line":"import time"},{"line_number":348,"context_line":""},{"line_number":349,"context_line":"from swift.cli.container_deleter import make_delete_jobs"},{"line_number":350,"context_line":"from swift.common.request_helpers import get_response_body"},{"line_number":351,"context_line":"from swift.common.header_key_dict import HeaderKeyDict"},{"line_number":352,"context_line":"from swift.common.exceptions import ListingIterError, SegmentError"},{"line_number":353,"context_line":"from swift.common.middleware.listing_formats import \\"}],"source_content_type":"text/x-python","patch_set":14,"id":"8e88b1b5_c02d790b","line":350,"range":{"start_line":350,"start_character":5,"end_line":350,"end_character":33},"in_reply_to":"2ab5a618_0138b1c5","updated":"2025-05-08 16:29:27.000000000","message":"Done","commit_id":"d0d177c933a73ba9cd45f89ea00b6e63a2e200ed"}],"swift/common/request_helpers.py":[{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"6456ec9fc06e18b5161a07beb46e25f7d4870d8f","unresolved":true,"context_lines":[{"line_number":1009,"context_line":"    return \u0027,\u0027.join(environ.get(\u0027swift.log_info\u0027, []))"},{"line_number":1010,"context_line":""},{"line_number":1011,"context_line":""},{"line_number":1012,"context_line":"def get_response_body(data_format, data_dict, error_list, root_tag):"},{"line_number":1013,"context_line":"    \"\"\""},{"line_number":1014,"context_line":"    Returns a properly formatted response body according to format."},{"line_number":1015,"context_line":""}],"source_content_type":"text/x-python","patch_set":14,"id":"fd8a63f6_cffd41e1","line":1012,"range":{"start_line":1012,"start_character":4,"end_line":1012,"end_character":21},"updated":"2025-05-08 10:26:03.000000000","message":"Great that we moved this, but I wonder if the name is now too generic. Maybe `get_heartbeat_response_body`? or could it be used in more places.\n\nThis is only a NIT.","commit_id":"d0d177c933a73ba9cd45f89ea00b6e63a2e200ed"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"c425514780783ddbd13f5e7fcf3ddb624c399139","unresolved":false,"context_lines":[{"line_number":1009,"context_line":"    return \u0027,\u0027.join(environ.get(\u0027swift.log_info\u0027, []))"},{"line_number":1010,"context_line":""},{"line_number":1011,"context_line":""},{"line_number":1012,"context_line":"def get_response_body(data_format, data_dict, error_list, root_tag):"},{"line_number":1013,"context_line":"    \"\"\""},{"line_number":1014,"context_line":"    Returns a properly formatted response body according to format."},{"line_number":1015,"context_line":""}],"source_content_type":"text/x-python","patch_set":14,"id":"58e040d8_269e3c70","line":1012,"range":{"start_line":1012,"start_character":4,"end_line":1012,"end_character":21},"in_reply_to":"fd8a63f6_cffd41e1","updated":"2025-05-08 16:29:27.000000000","message":"Done","commit_id":"d0d177c933a73ba9cd45f89ea00b6e63a2e200ed"}],"swift/common/utils/get_response_body.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a6070f00a375b9604d4fa01713cef0f6a360a690","unresolved":true,"context_lines":[{"line_number":17,"context_line":"from xml.sax.saxutils import escape  # nosec B406"},{"line_number":18,"context_line":""},{"line_number":19,"context_line":""},{"line_number":20,"context_line":"def get_response_body(data_format, data_dict, error_list, root_tag):"},{"line_number":21,"context_line":"    \"\"\""},{"line_number":22,"context_line":"    Returns a properly formatted response body according to format."},{"line_number":23,"context_line":""}],"source_content_type":"text/x-python","patch_set":8,"id":"ef062563_48e607cf","line":20,"updated":"2025-04-28 22:52:28.000000000","message":"Solid extraction -- it was weird that `slo` was importing from `bulk`. I wonder if it might fit in well in `swift/common/request_helpers.py`, though?\n\n(You might also consider doing this as a pre-requisite refactor, just to focus this review on the new feature, but it\u0027s not _that_ much extra to keep in mind as a reviewer.)","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"12900d8d1001404d3adc1bcac48941431d04a978","unresolved":false,"context_lines":[{"line_number":17,"context_line":"from xml.sax.saxutils import escape  # nosec B406"},{"line_number":18,"context_line":""},{"line_number":19,"context_line":""},{"line_number":20,"context_line":"def get_response_body(data_format, data_dict, error_list, root_tag):"},{"line_number":21,"context_line":"    \"\"\""},{"line_number":22,"context_line":"    Returns a properly formatted response body according to format."},{"line_number":23,"context_line":""}],"source_content_type":"text/x-python","patch_set":8,"id":"c927ccd6_bc6c2672","line":20,"in_reply_to":"ef062563_48e607cf","updated":"2025-05-01 01:16:41.000000000","message":"Done","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"}],"test/unit/common/middleware/test_copy.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a6070f00a375b9604d4fa01713cef0f6a360a690","unresolved":true,"context_lines":[{"line_number":14,"context_line":"# See the License for the specific language governing permissions and"},{"line_number":15,"context_line":"# limitations under the License."},{"line_number":16,"context_line":""},{"line_number":17,"context_line":"import mock"},{"line_number":18,"context_line":"import unittest"},{"line_number":19,"context_line":"import urllib.parse"},{"line_number":20,"context_line":"import eventlet"}],"source_content_type":"text/x-python","patch_set":8,"id":"78bb2ca9_75b33c84","line":17,"updated":"2025-04-28 22:52:28.000000000","message":"Sorry, I got rid of this in https://review.opendev.org/c/openstack/swift/+/943502\n\nIf you just switch it back to `from unittest import mock` tests should pass.","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"},{"author":{"_account_id":37337,"name":"Nathan Nguyen","display_name":"Nathan Nguyen","email":"nguyennathan1502@gmail.com","username":"nathang15"},"change_message_id":"12900d8d1001404d3adc1bcac48941431d04a978","unresolved":false,"context_lines":[{"line_number":14,"context_line":"# See the License for the specific language governing permissions and"},{"line_number":15,"context_line":"# limitations under the License."},{"line_number":16,"context_line":""},{"line_number":17,"context_line":"import mock"},{"line_number":18,"context_line":"import unittest"},{"line_number":19,"context_line":"import urllib.parse"},{"line_number":20,"context_line":"import eventlet"}],"source_content_type":"text/x-python","patch_set":8,"id":"47f462de_600b0c65","line":17,"in_reply_to":"78bb2ca9_75b33c84","updated":"2025-05-01 01:16:41.000000000","message":"Done","commit_id":"5436a6298fc68c23971f3870f0131f382602e8a0"}]}
