raulcd commented on code in PR #50687:
URL: https://github.com/apache/arrow/pull/50687#discussion_r3673826598


##########
python/pyarrow/tests/test_flight.py:
##########
@@ -1128,6 +1130,43 @@ def test_flight_server_location_argument():
             assert isinstance(server, FlightServerBase)
 
 
+# The following tests are for GH-50684, which was a memory leak
+# in FlightServerBase.
+def test_flight_server_is_freed():
+    # Calling server.shutdown manually should free the server object.
+    server = FlightServerBase('grpc://localhost:0')
+    server.shutdown()
+    server.wait()
+    ref = weakref.ref(server)
+    del server
+    gc.collect()
+    assert ref() is None
+
+
+def test_flight_server_is_freed_on_exit():
+    # Using FlightServerBase as a context manager should free
+    # the server object on exit.
+    with FlightServerBase('grpc://localhost:0') as server:
+        ref = weakref.ref(server)
+    del server
+    gc.collect()
+    assert ref() is None
+
+
[email protected](
+    reason="GH-50684: FlightServerBase is not freed on delete without shutdown"
+)
+def test_flight_server_is_freed_without_shutdown():
+    # GH-50684: Not calling server.shutdown() currently leaks the server 
object.
+    # This test is expected to fail until the issue is fixed but is included
+    # for completeness and further discussion.

Review Comment:
   I've reproduced the hang with the following small snippet and a local PoC 
moving to a weakref, done is never printed, it  hangs forever:
   ```python
   import threading
   import time
   
   import pyarrow as pa
   import pyarrow.flight as fl
   
   
   class Server(fl.FlightServerBase):
       def do_get(self, context, ticket):
           time.sleep(2)
           return fl.RecordBatchStream(pa.table({"a": [1]}).to_reader())
   
   
   server = Server("grpc://127.0.0.1:0")
   location = f"grpc://127.0.0.1:{server.port}"
   
   
   def client():
       fl.FlightClient(location).do_get(fl.Ticket(b"")).read_all()
   
   
   thread = threading.Thread(target=client)
   thread.start()
   
   time.sleep(1)
   del server
   
   thread.join()
   print("done")
   ```
   With py-spy dump I can see the idle thread and part of the stack pointing to 
the DoGet thread calling shutdown, this is trimmed:
   ```gdb
       grpc_completion_queue_next (completion_queue.cc:1154)
       grpc::CompletionQueue::AsyncNextInternal (completion_queue_cc.cc:147)
       grpc::CompletionQueue::AsyncNext<gpr_timespec> (completion_queue.h:202)
       grpc::Server::ShutdownInternal (server_cc.cc:1296)
       grpc::ServerInterface::Shutdown (server_interface.h:106)
       Shutdown (grpc_server.cc:649)
       arrow::flight::FlightServerBase::Shutdown (server.cc:216)
       _FlightServerFinalizer_finalize (pyarrow/_flight.pyx:2885)
       finalize (pyarrow/_flight.pyx:2877)
       __Pyx_CyFunction_Vectorcall_FASTCALL_KEYWORDS (pyarrow/_flight.pyx:1666)
       __call__ (weakref.py:590)
       __pyx_tp_dealloc_7pyarrow_3lib__Weakrefable (pyarrow/public-api.pxi:439)
       __pyx_tp_dealloc_7pyarrow_7_flight_FlightServerBase 
(pyarrow/_flight.pyx:3251)
       Py_XDECREF (object.h:1049)
       arrow::py::OwnedRef::reset (common.h:202)
       arrow::py::OwnedRef::reset (common.h:205)
       arrow::py::OwnedRef::~OwnedRef (common.h:198)
       operator() (flight.cc:160)
       SafeCallIntoPython<arrow::py::flight::PyFlightServer::DoGet(const 
arrow::flight::ServerCallContext&, const arrow::flight::Ticket&, 
std::unique_ptr<arrow::flight::FlightDataStream>*)::<lambda()> > (common.h:161)
       arrow::py::flight::PyFlightServer::DoGet (flight.cc:161)
       arrow::flight::internal::ServerTransport::DoGet (transport_server.cc:301)
       DoGet (grpc_server.cc:493)
       operator() (Flight.grpc.pb.cc:300)
       __invoke_impl<grpc::Status, 
arrow::flight::protocol::FlightService::Service::Service()::<lambda(arrow::flight::protocol::FlightService::Service*,
 grpc::ServerContext*, const arrow::flight::protocol::Ticket*, 
grpc::ServerWriter<arrow::flight::protocol::FlightData>*)>&, 
arrow::flight::protocol::FlightService::Service*, grpc::ServerContext*, const 
arrow::flight::protocol::Ticket*, 
grpc::ServerWriter<arrow::flight::protocol::FlightData>*> (invoke.h:63)
   ```
   This is not happening today because the strong reference enforces refcount > 
0, we have to manually call shutdown for the reference to be released which 
should never be done from a serving thread as the method documents. I am now 
wondering whether not calling shutdown and keeping the Python object alive 
should be the intended behavior instead of being treated as a bug leaking an 
object on this case.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to