Giter Club home page Giter Club logo

Comments (12)

dgelessus avatar dgelessus commented on June 11, 2024 1

Right, that's the usual problem with passing local variables to C functions - if you're not careful, the Python object can get deleted while the C code still has a pointer to it.

You're right, at the moment Rubicon does nothing to ensure that the Python callable stays alive for the lifetime of the block, but that should be possible to implement. Blocks can contain "copy" and "dispose" callbacks that are called when a block is copied and deleted, respectively. These could be used to manage a global collection of Python objects that need to be kept alive, based on the block in question.

from rubicon-objc.

cculianu avatar cculianu commented on June 11, 2024

UPDATE: I think I figured out why I was getting crashes! Duh! With Asynch block code, sometimes Python cleans up temporaries by the time your callable gets called - the Block type needs to be stored somewhere. Then I don't get crashes.

Still.. it would be nice to support 'None' as a block param. That definitely doesn't work quite right.

Anyway -- it would also be nice to have the Block wrapper have the same semantics as obj-c -- namely keep the Block wrapper around until it's released. Is that possible? Perhaps some obj-c level observer on the obj-c level Block object type?

from rubicon-objc.

cculianu avatar cculianu commented on June 11, 2024

Ok, right. That sounds like a good approach.

Is such a 'keepalive' table implemented at all on the rubicon side for other objc objects? I remember seeing something like that somewhere in runtime.py or somesuch.

When I get some bandwidth I can look into this too.

Also -- support for 'None' to be passed as an argument that expects a block is straightfoward (I would imagine) -- I can take a stab at that.

from rubicon-objc.

dgelessus avatar dgelessus commented on June 11, 2024

Yes, there is the imp_keepalive_table on ObjCClasses defined in Python, which do basically the same thing, but for the Python callables used to implement Objective-C methods. For blocks, we'd probably need a global table indexed by the block address, or something similar.

from rubicon-objc.

cculianu avatar cculianu commented on June 11, 2024

Ok, so I am getting ready to take a stab at implementing this.

To save me tons of time -- could you perhaps tell me if objc sends a message or some other notification when an objc block is about to be deallocated? Or is it possible to install an observer on a block? Do you know offhand?

from rubicon-objc.

dgelessus avatar dgelessus commented on June 11, 2024

Blocks are a bit special - although they are most commonly used in Objective-C code, they are an independent feature that can also be used in regular C and C++ (if the compiler supports it obviously). Because of this, the internals of blocks do not use Objective-C classes and messages at all. Instead, blocks are defined as structs following a certain format, described in the Block Implementation Specification.

For this feature in particular, you'll want to look at the copy_helper and dispose_helper callbacks that can be added to the block descriptor. They are called whenever a block is copied and deallocated, respectively. I think we could use these callbacks to manage a table (dict) on the Python side, which keeps track of the Python objects that each block requires. We'd add each block that we create to the table, the copy_helper would copy the table entry when the block in question is copied, and the dispose_helper would remove the table entry of the block in question.

from rubicon-objc.

cculianu avatar cculianu commented on June 11, 2024

Hey -- you read my mind! I was just looking at that as the potential solution!

So -- to reiterate this, broadly I imagine:

  1. Copy helper detects that the objc block was retained(copied) and adds the Python-level "Block" instance to a table/dict, keyed off the new pointer. The Block object is internally updated if a new objc pointer was created (i'm not clear on whether this very last bit is relevant for us.. perhaps the pointer always stays the same?)
  2. Dispose helper detects that the objc block was disposed of (deleted) and removes the corresponding Python-level "Block" instance from the tabe/dict

This way the semantics on the Python side remain identical to what you expect in objc, where you can willy-nilly have blocks inline and they get auto-retained/copied (internally Block_copy() is called) when they are assigned to retain properties, etc.

Am I sort of getting it?

I'm still fuzzy on the python side's rep of blocks but reading the code I should eventually get it. Admittedly I am not the most familiar with Python/C integration but this is a good learning opportunity.

from rubicon-objc.

cculianu avatar cculianu commented on June 11, 2024

Update: I have a working implementation! Will clean it up and submit it for your review today (hopefully)!

Woo hoo!

from rubicon-objc.

cculianu avatar cculianu commented on June 11, 2024

Done. PR #105

Please do let me know if it's appropriate. I didn't bother copying back the python block descriptor structs on copy (since I suck at C/Python integration APIs and it seemed a chore). I just updated the objc_id ptr, since that's all the obj-c side cares about, and the Python side doesn't seem to read those python structs .. and if it does the data still seems valid to me.

Anyway.. It shouldn't be a problem really.. I hope. :)

from rubicon-objc.

dgelessus avatar dgelessus commented on June 11, 2024

(Note: I see you've submitted a PR while I was typing this, so you probably know all of this already.)

That all sounds correct to me.

Regarding the copy helper: as I understand it, it's only called when an actual copy is created. If I'm reading the source code correctly, that only happens for stack blocks - for other types, the copy operation just increments a refcount and doesn't create a separate object. Since our current implementation marks all blocks we create as global, I think we could theoretically ignore the copy helper, but I would still implement it just to be sure.

Now that I think about it, marking our blocks as global may be a problem here. The _Block_release function does nothing for global blocks, which means that our dispose helper would never be called either...

from rubicon-objc.

cculianu avatar cculianu commented on June 11, 2024

As far as I know Block copy is also called whenever a block is retained. The semantics of it are basically "if required, make a persistent copy of all temporaries", which is why it's not called block_retain, and block_copy, since really it can do more than just retain pointers. Behind the scenes stack variables that the block references get their own heap copy and pointers are updated appropriately (I think).

FYI -- in my experimentation with the very PR I posted, putting print statements in, I did get a different pointer each time copyHelper was invoked, whatever that means. I am supposing that the block does end up getting copied.

I am not sure I understand your last paragraph -- I did observe disposeHelper being called for blocks that objc took ownership of.

I suspect that blocks that never give ownership to ObjC (ones that get invoked immediately and not retained) shouldn't be a problem -- copyHelper() doesn't get invoked on our side, so they aren't stored globally (they only get stored globally when copyHelper() gets invoked!).

If copyHelper() got invoked, disposeHelper() should get invoked, which removes any block entries from the global table.

I think it's right. Though I am not sure 100%. Any insight is appreciated.

from rubicon-objc.

cculianu avatar cculianu commented on June 11, 2024

Closed as merged PR #105 implements this.

from rubicon-objc.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.