Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Ngăn chặn rò rỉ bộ nhớ khi gọi các phương thức Ruby từ phần mở rộng C

Rò rỉ bộ nhớ là một nỗi đau đối với người dùng đá quý. Chúng khó theo dõi và có thể dẫn đến chi phí cơ sở hạ tầng đắt đỏ.

Rò rỉ bộ nhớ trong phần mở rộng C thậm chí còn tồi tệ hơn. Bạn sẽ thấy rất nhiều công cụ và bài viết về việc tìm kiếm các lỗ hổng trong Ruby. Tuy nhiên, bạn không có quyền truy cập tương tự vào nội bộ trong C.

Cách sử dụng rb_funcall một cách ngây thơ có thể gây rò rỉ bộ nhớ:sử dụng rb_protect sẽ tốt hơn nhiều thay vào đó. Vì vậy, nếu bạn là người viết phần mở rộng C, vui lòng đọc tiếp vì lợi ích của các nhà phát triển sẽ sử dụng viên ngọc quý của bạn.

Hãy bắt đầu!

Vấn đề với rb_funcall và C

rb_funcall có thể là một công cụ tuyệt vời khi bạn cần tương tác giữa Ruby và phần C của thư viện nhưng chỉ cần viết một chút C.

Tuy nhiên, khi bạn chạy rb_funcall , bạn không còn ở C nơi mọi thứ đều đơn giản. Bạn có thể bị bỏ lại trong vùng nước bùn nếu hàm được gọi:

  1. Thay đổi hoàn toàn định nghĩa của nó trong thời gian chạy
  2. Thực hiện cuộc gọi

Số 1 là dễ bắt nhất. Bạn có thể sẽ gặp phải lỗi phân đoạn và nếu bộ thử nghiệm của bạn đủ hoàn chỉnh, bạn nên nắm bắt điều đó trước khi xuất bản.

Tuy nhiên, điều sau có thể gây rò rỉ bộ nhớ và khiến cơ sở mã của bạn khó đọc hơn. Bây giờ chúng ta hãy xem xét điều đó.

Tăng cường Ruby gây rò rỉ bộ nhớ C

Cơ chế nâng cao của Ruby nhảy giữa các phần của mã từ phạm vi này sang phạm vi gốc đầu tiên và phát hiện lỗi. Điều này được triển khai trong MRI bằng cách sử dụng longjmpsetjmp .

Nếu bạn quan tâm đến cách xây dựng nó, hãy đọc chương Người đánh giá trong Hướng dẫn hack Ruby. Tóm lại, khi bạn sử dụng begin..ensure chặn, bạn setjmp() , và khi bạn raise trong khối này, bạn longjmp() đến vị trí đã lưu.

Vì vậy, nếu một hàm được nâng lên với rb_funcall , mã C được gọi sau khi nó không bao giờ thực thi.

Ví dụ dưới đây minh họa khả năng rò rỉ. Nếu json_parse tăng lên, nó sẽ rò rỉ.

 

Tất nhiên, ví dụ trên hơi ngớ ngẩn - bạn có thể đảo ngược phần giải phóng và xử lý Ruby. Tuy nhiên, điều này không phải lúc nào cũng thực hiện được và các phần chức năng dài hơn có thể trở nên gắn bó với nhau hơn.

Sử dụng begin..ensure trong Ruby

Nếu bạn đang sử dụng Ruby, thay vào đó bạn có thể viết ví dụ trên bằng cách sử dụngbegin..ensure :

 

API này cũng có sẵn trong C với rb_rescuerb_ensure :

 

Tuy nhiên, điều này hơi cồng kềnh và nếu bạn muốn thêm rescue chặn bữa tiệc, nó trở nên khó đọc hơn. Tôi khuyên bạn nên đọc 'A Rubyist's Walk Together the C-side (Phần 8):Ngoại lệ &Xử lý lỗi' của Peter Zhu nếu bạn muốn sử dụng begin..rescue..ensure..end API trong C.

Sử dụng rb_protect cho C

Có một lựa chọn khác. Trước tiên, hãy xem nó trông như thế nào trong Ruby:

 

Điều này có vẻ lạ trong Ruby, nhưng là một quy trình làm việc rất phù hợp với C. MRI có API cho việc đó, rb_protect , và hàm C trông như thế này:

 

Phương pháp trên sẽ gây ra lỗi Ruby sau khi giải phóng mọi thứ.

Lưu ý rằng chúng ta cũng có thể chọn bỏ qua lỗi bằng cách sử dụng rescue trống khối trong Ruby:

 

Cảnh báo: Nếu bạn không đưa ra lỗi, rb_set_errinfo(Qnil) bước này rất quan trọng để bạn không giữ sẵn thông tin về lỗi mà người dùng không nên biết.

Hoặc, bạn có thể chọn đưa ra lỗi một cách có điều kiện, như rescue My::Error :

 

Bạn thực sự có thể xem xét rb_errinfo() giống như $! biến toàn cục.

Điều này thật tuyệt vời, nhưng khi tổng cộng chỉ còn một rb_funcall duy nhất, chúng tôi có thể đơn giản hóa API đó.

Ý tưởng tổng thể đằng sau việc sử dụng rb_protect API khi có chức năng cần nâng cao là để tăng cường khả năng đọc. Bạn không cần kiểm tra xem hàm có thể tăng hay không, bạn cho rằng nó có thể và sử dụng trạng thái để làm việc với điều đó.

rb_protect_funcall Đề xuất

Hãy cách ly rb_funcall , vì đó là nguy hiểm duy nhất phương pháp sử dụng. Đây là API sẽ làm điều đó:

 

API này giống với rb_funcall , với state từ rb_protect . Do đó cách sử dụng khá đơn giản:

 

API này chưa có sẵn trong Ruby và có thể không bao giờ có. Bạn có thể lấy nó từRGeo (MIT LICENSE).

Một ví dụ thực tế

Nếu bạn muốn xem ví dụ thực tế, tôi khuyên bạn nên đọc RGeocodebase vì gần đây chúng tôi đã chuyển sang sử dụng rb_protect đầy đủ . Chúng tôi thậm chí còn có một số chức năng, chẳng hạn như rgeo_convert_to_geos_geometry , truyền bá trạng thái này để sử dụng đơn giản hơn. Chức năng này là một nơi tốt để bắt đầu tìm hiểu.

Vui lòng mở một vấn đề trên RGeo để thảo luận thêm về những lựa chọn mà chúng tôi đã đưa ra.

Kết thúc

Trong bài đăng này, chúng tôi đã cảnh báo không nên sử dụng rb_funcall với C vì nó có thể gây rò rỉ bộ nhớ. Chúng tôi đã khám phá bằng cách sử dụng begin..ensure hoặc rb_protect thay vào đó.

Chúc bạn viết mã vui vẻ!

Tái bút. Nếu bạn muốn đọc các bài đăng của Ruby Magic ngay khi chúng được đăng tải, hãy đăng ký nhận bản tin Ruby Magic của chúng tôi và không bao giờ bỏ lỡ một bài đăng nào!

Ngăn chặn rò rỉ bộ nhớ khi gọi các phương thức Ruby từ phần mở rộng C

Ulysse Buônomo

Tác giả khách mời Ulysse của chúng tôi là một cựu nhà phát triển Ruby trong ngành, người dành phần lớn thời gian của mình để đi du lịch vòng quanh thế giới. Thời gian rảnh rỗi của anh ấy dành cho RGeo và Ruby, đồng thời anh ấy thích mày mò nghiên cứu nội bộ của Ruby.

Tất cả bài viết của Ulysse Buonomo