Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
How to Support Multipeer Connectivity ?
-
I have put up the two pieces of code above on Github:
Advertiser
BrowserThey work fine on first run, but the advertiser/server always crashes on the second run.
It only crashes if there was a successful connection on the first run, otherwise it runs fine.
Fault handler provides only a semi-useful error:
Fatal Python error: Segmentation fault Current thread 0x000000016fffb000 (most recent call first): File "/var/containers/Bundle/Application/01E95FCB-DD81-45FB-B878-47D3C0FF9E35/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 682 in __del__
... which points to the last line in this objc_util code:
def __del__(self): # Release the ObjC object's memory: objc_msgSend = c['objc_msgSend'] objc_msgSend.argtypes = [c_void_p, c_void_p] objc_msgSend.restype = None objc_msgSend(self.ptr, sel('release'))
Changes I have tried to the code in this thread, with no effect:
- Added session
disconnect()
to the end - Added try/except to only create the delegate classes when they do not already exist
Any help debugging this would be much appreciated.
- Added session
-
You could try to figure out which object is crashing, by manually del'ing them, one at a time. (my guess - services are getting del'd before delegates). Then, add an extra retain() for the offending objects.
Alternatively, maybe try setDelegate_(None) on everything that takes a delegate, to disentangle things.
-
@JonB, thanks.
Trying to run incrementally more code after the crash to find the culprit failed, as Pythonista will crash if I execute just an empty file.
Setting delegates to None has not changed things.
Manually
del
eting either the session object or the advertiser object both cause a crash after a successful connect, but do not cause a crash if I do the deletes after running the server code without a connection. -
@JonB, as this is the code that gets run on an invite, which seems to be the crucial point causing the crashes, and as I understand next to nothing about it, I have to ask if there is anything you see that messes things up?
class _block_descriptor (Structure): _fields_ = [('reserved', c_ulong), ('size', c_ulong), ('copy_helper', c_void_p), ('dispose_helper', c_void_p), ('signature', c_char_p)] InvokeFuncType = ctypes.CFUNCTYPE(None, *[c_void_p,ctypes.c_bool,c_void_p]) class block_literal(Structure): _fields_ = [('isa', c_void_p), ('flags', c_int), ('reserved', c_int), ('invoke', InvokeFuncType), ('descriptor', _block_descriptor)] # Advertiser Delegate def advertiser_didReceiveInvitationFromPeer_withContext_invitationHandler_(_self,_cmd,advertiser,peerID,context,invitationHandler): print('invitation',peerID) blk=block_literal.from_address(invitationHandler) blk.invoke(ObjCInstance(invitationHandler),True, ObjCInstance(mySession))
-
@mikael said:
advertiser_didReceiveInvitationFromPeer_withContext_invitationHandler_
Hmm, okay:
can you print (console).retainCount()
for the advertiser and session objects, for the case when no invite happens, then after the invite occurs?What I am wondering is if something is getting released by the invitationHandler -- that bit of code is taking the block that is passed to the delegate method, turning it into an actual ObjCBlock, then invoking it.
One thing you could do is override the
__del__
method of the suspect ObjCInstances -- maybe set up logging to a file in your script, then insert a logger.debug(self), that way we can see which object is the culprit.Next, you could try first calling
retain()
on those instances -- that may cause a memory leak, but probably not a crash. -
Thank you.
Before a connection, both advertiser and session report a retainCount of 2.
After a connection has been established and closed, calling retainCount on either object crashes Pythonista.
Printing counts within the callback, before the invitation invoke gives these:
- Advertiser: 5
- Session: 2
And immediately after:
- Advertiser: 6
- Session: 4
-
Have you tried calling retain, then del the object?
Alternatively -- just monkey patch
__del__
so it is a pass only. -
@JonB, thanks.
Tried mySession.retain() on the console after a connection —> crash.
Tried retain() on both session and advertiser immediately after creation, then del’ing them on close. Next run still crashes.
Tried setting
__del__
of both objects to a dummy. Next run still crashes inobjc_util.__del__
, likewise if I try adel mySession
on console, so I am probably not monkeypatching properly. -
Hi Mikael,
had the same problem. The crash was caused by the garbage collection which is triggered automatically somewhen when python wants to delete the invitationHandler.
My approach/workaround was not to get it deleted by remembering its reference. That way the garbage collector will not call delete on it.
instead of
blk.invoke(ObjCInstance(invitationHandler),True, ObjCInstance(mySession))
try
inviHandler = ObjCInstance(invitationHandler)
someglobalscopeobject.remember_this_thing=inviHandler
blk.invoke(inviHandler,True, ObjCInstance(mySession))my guess was that something in the MPC framework still holds the reference and uses this invitation handler but the pythonista garbage collector does not know this because it only knows about references in your python programm. Thus when the python gc deletes it, and after that something in the apple mpc framework wants to call it, it will inevitably crash.
EDIT:
instead of someglobalscopedobject you could also use something like that to make it able to be reentrantimport builtins
...
inviHandlerObj = ObjCInstance(invitationHandler)if (builtins.retainCache == NONE):
builtins.retainCache = [ ]
builtins.retainCache.append(inviHandlerObj)blk.invoke(inviHandlerObj,True, ObjCInstance(mySession))
... -
@mithrendal, thanks! @JonB was right, but I just missed the mark.
I used the objc_util.retain_global, and updated the repository with the new version. Next I will package this into a Pythonic API that does not have client-server roles but only peers.
# Advertiser Delegate def advertiser_didReceiveInvitationFromPeer_withContext_invitationHandler_(_self,_cmd,advertiser,peerID,context,invitationHandler): global mySession invitation_handler = ObjCInstance(invitationHandler) retain_global(invitation_handler) blk=block_literal.from_address(invitationHandler) blk.invoke(invitation_handler,True, mySession)
-
Hi @mikael,
Yes that looks promising. I just uploaded my work on this for you at github. It doesn't crash and is completely reentrant. The idea was to make an simple API which games and other apps can import. But I did not work on it recently. When I saw your post that you too want to build an API I thought you might want to have a look. https://github.com/mithrendal/pythonista_mpc
Btw I am a big fan of your Gestures.py API 😀
-
How do I send data from client to server and vise versa during game?
-
@robertiii, check examples in @mithrendal’s code, linked in his post above. Or hold for my Grand Pythonic Simplification.
-
@robertiii be aware, my code on github is still unfinished. I did this some months ago to proof that MPC is really working in Pythonista . Thats what the code does well. I uploaded it then for @mikael as an working example. But it does not yet serve as an solid and simple API, a lot of polishing and refactoring is still needed here. I am still at it, but my time for it is restricted so don't expect a finished API very soon. Of course you decide, but I would wait for @mikael 's Grand Python Simplyfication !!! That is what I am also looking forward to ;-) because he has already done some great APIs for pythonista (see here https://github.com/mikaelho).
-
Ok, with expectations set unreasonably high by @mithrendal, please check the repo for the first working version of multipeer.py.
Here's a minimal usage example, a line-based chat:
import multipeer my_name = input('Name: ') mc = multipeer.MultipeerConnectivity( display_name=my_name, service_type='chat' ) try: while True: chat_message = input('Message: ') mc.send(chat_message) finally: mc.end_all()
This is a functional chat, even though the prompts and incoming messages tend to get messily mixed up.
You can also run the
multipeer.py
file to try out a cleaner Pythonista UI version of the chat, which demonstrates sending dicts as the message content, and acts as the best 'how-to guide' at the moment.Docs still need some significant work. All testing and comments highly welcome.
-
Noted that shutting and restarting the UI chat client leads to duplicate messages being received, at least with 3 peers, and general instability. I will try including the checks to not re-create classes when already available.
Also, need to implement proper error handling and exceptions if possible.
-
btw, those try/except to not recreate the classes would not have worked,
e.g.
SessionDelegate.alloc().init()
would fail because SessionDelegate was cleared.
You might first try
SessionDelegate=ObjCClass('SessionDelegate')though, note that the objc class name can change when using "debug" mode.
-
@JonB, thanks. I would like to say that I understood completely what you said... But no, I didn’t. For clarity, I understood that the thing below would be sufficient, barring debug on/off people?
try: AdvertiserDelegate = ObjCClass('AdvertiserDelegate') ADelegate = AdvertiserDelegate.alloc().init() except: # ...
-
Eh, the issue was with the UI chat demo code, not the MC framework. Seems to work pretty well now, even tolerates and recovers from being backgrounded without any additional effort from the developer.
Next I will take a look at capturing and managing errors from send, mainly.
Would be good to hear if someone has a use case for the streaming or resource-send options, which are much more tricky.
-
create_objc_class will create a new class name every time it is run (at least, with the default debug attrib set to 1, ) -- so
'AdvertiserDelegate'
might really become'AdvertiserDelegate1'
.Only really an issue for development, but if you see strange things where a change you just made doesn't seem to be working, thats the reason.