trouble setting output ports to data from asynchronously loaded urls

adamfenn28's picture

I'm pretty new to Objective-C, but I'm about 95% complete with a suite of QC plugins.

I'd like to get data asynchronously with NSURLConnection. When I try to set the self.outputResult (or any output port) from within the the delegate ( I think that's where I'm at), it fails.

When I try, with:

self.outputResult = [NSString stringWithFormat:@"%@", txt];

It crashes with:

-[timeoutPlugIn_setValue:forOutputPort:]:Argument "port" cannot be null

My project is attached. It's super simple, just testing this one piece.

Thanks for the help! Adam

PreviewAttachmentSize
timeout.zip2.43 MB

cwright's picture
Re: trouble setting output ports to data from asynchronously ...

First: Before you archive a project, PLEASE DELETE THE BUILD DIRECTORY (HOLY COW EVERYONE DOES THIS, DON'T FEEL BAD). [xcode4 presumably fixes this?] {This totally belongs in a FAQ, and the attachment receiver should crash the webserver whenever a .zip is attached with a /build directory or something}

Archiving the build directory makes the archive ridiculously large. Your attachment is 2.4MB. about 4k of that is source code, and the rest are useless intermediate files.

Second, posting a backtrace helps us seasoned, grizzled code slayers with knowing what you did wrong at a glance. You've only provided the top-most stack frame. I'm not going to build/install this code, so the remainder are just guesses.

NSMutableData *responseData; What is this? is it a global? is it supposed to be an ivar? If it's an ivar of the class, it needs to be between the curly braces of the class interface, not outside (outside means something different -- a global variable, which, if that's the intent, shouldn't be inside the @interface block at all.)

You've got a line like this in -execute:

 [[NSURLConnection alloc] initWithRequest:request delegate:self];

Congratulations, that leaks and then leaks request too. per frame. don't do this.

You're setting an output in a delegate method (-connectionDidFinishLoading:) that doesn't take place synchronously. In layman's terms, this means you're trying to change a port when QC isn't expecting you to do so. This is a violation of the QCPlugIn contract; as such, I wouldn't expect this to work properly (and crashes don't seem implausible).

The usual way to solve this problem is to have the patch execute every frame, and the async bits set a flag that tell execute that it needs to do stuff (set outputs, etc). then you store the results in an ivar that patiently waits for the next -execute::: call to do its thing.

adamfenn28's picture
Re: trouble setting output ports to data from asynchronously ...

Thanks for the info. I'll follow your suggestions.. Here's the rest of the error:

0x851dcdbc: GFException 0x851df119: GFThrowException 0x85264832: -[QCPlugIn(Ports) _setValue:forOutputPort:] 0x031dc8ad: -[timeoutPlugIn connectionDidFinishLoading:] /Users/afenn/Documents/timeout/timeoutPlugIn.m:111 0x87df0608: _NSURLConnectionDidFinishLoading 0x8044f1a0: URLConnectionClient::_clientDidFinishLoading(URLConnectionClient::ClientConnectionEventQueue*) 0x804b49ae: URLConnectionClient::ClientConnectionEventQueue::processAllEventsAndConsumePayload(XConnectionEventInfo<XClientEvent, XClientEventParams>*, long) 0x8043b825: URLConnectionClient::processEvents() 0x8043b600: MultiplexerSource::perform() 0x800a1401: __CFRunLoopDoSources0 0x8009f5f9: __CFRunLoopRun 0x8009edbf: CFRunLoopRunSpecific 0x8255a7ee: RunCurrentEventLoopInMode 0x8255a5f3: ReceiveNextEventCommon 0x8255a4ac: BlockUntilNextEventMatchingListInMode 0x80bcae64: _DPSNextEvent 0x80bca7a9: -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] 0x80b9048b: -[NSApplication run] 0x00001d2b

responseData is a global variable. It's not inside the @interface block at all.

So, the bottom line is that I can't set output ports in a delegate. I need to solve this in the usual way, like you described in the last paragraph. Right?

Thanks! Adam I think I understand that

cwright's picture
Re: trouble setting output ports to data from asynchronously ...

adamfenn28 wrote:
responseData is a global variable. It's not inside the @interface block at all.

But anyway: You can set ports in delegate methods iff they're synchronous; I don't know how common of a case that is though :/

There may be other patterns that work well for this kind of thing, but the approach I outlined is what I've found to work. There's no clear way to force a patch to need to execute (to tell QC to -execute::: once the delegate has done something useful), so you have to keep trying. it's icky, but there aren't any good alternatives that I can think of off the top of my head.

PreviewAttachmentSize
totallyInside.png
totallyInside.png18.65 KB

adamfenn28's picture
Re: trouble setting output ports to data from asynchronously ...

I think I've stumbled on an ever bigger problem. Because processor patches only run when input values change, and I'm trying to get this URL asynchronously, I can't read the result until the input values change. If I do this, it creates a lag between the results being retrieved and the results being processed. In my use case, this is undesirable.

It seems it would be more simple, and with less lag, if I were to do this synchronously. Am I thinking of this correctly?

I need to specify a subs-second timeout, and I've had a difficult time finding a way to specify a timeout for synchronous connections.

adamfenn28's picture
Re: trouble setting output ports to data from asynchronously ...

Here's the code I've tried to use to pull URLs synchronously, with a timeout.

   NSURL *myURL = [NSURL URLWithString:url];
   NSURLRequest *urlRequest = [NSURLRequest requestWithURL:myURL
                                                                      cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                                                timeoutInterval:1];
   NSURLResponse *response = nil;
   NSError *error = nil;
   NSData *data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error];
   NSString *string = [[NSString alloc] initWithData:data encoding: NSUTF8StringEncoding ];

It works, but seems to ignore the timeoutInterval:1. Any idea how I can get this to work synchronously?

jstrecker's picture
Re: trouble setting output ports to data from asynchronously ...

It seems it would be more simple, and with less lag, if I were to do this synchronously. Am I thinking of this correctly?

Yes, doing it synchronously should be easier. Less to worry about.

It works, but seems to ignore the timeoutInterval:1. Any idea how I can get this to work synchronously?

It seems to work for me. I went to a URL that took > 1 second to load and it timed out. What problem are you having?

By the way -- a plug for Kineme NetworkTools. We'll be releasing a NetworkTools plugin soon -- it will include a HTTP query patch that does both synchronous and asynchronous queries.

gtoledo3's picture
Re: trouble setting output ports to data from asynchronously ...

Quote:
By the way -- a plug for Kineme NetworkTools. We'll be releasing a NetworkTools plugin soon -- it will include a HTTP query patch that does both synchronous and asynchronous queries.

That sounds like it's going to be amazing! Will we be able to upload info to web in any way?

jstrecker's picture
Re: trouble setting output ports to data from asynchronously ...

Yes, by sending a POST query, which can include a body.

adamfenn28's picture
Re: trouble setting output ports to data from asynchronously ...

I just gave it a bogus URL on IP address 123.134.33.11 and it took 3 minutes (on a stopwatch) to error out. I just used the code that I pasted here.

Maybe your connection worked because the page started to load. Should I be looking for some network layer timeout?

jstrecker's picture
Re: trouble setting output ports to data from asynchronously ...

I don't know. When I put in the same IP address, it times out after a second.

adamfenn28's picture
Re: trouble setting output ports to data from asynchronously ...

I've tried the code I posted before. I've tried the Kineme String With URL patch. I've tried [NSString stringWithContentsOfURL:encoding:. All hang for about 3 minutes when I enter a url based on the IP address 127.0.0.2, which doesn't exist on my computer. I suspect one of two things.

1) The timeoutInterval only starts after the connection is established, and never kicks in. In which case, I need to find a way to adjust another timer, that will kill the connection after a timeout, prior to the connection being established. Is this right? Is there such a timer I can adjust?

2) All of the methods I've tried are synchronous, and that's why they lock the application up while they are timing out, unable to reach the target host. Is this the case? Would going to back to async solve my problem? If that's the case, I'm still having trouble getting async to work easily inside a patch. I'm not sure how to get execute:atTime:withArguments: to fire off when the delegate has my response. Any help is appreciated!

Thanks!

smokris's picture
Re: trouble setting output ports to data from asynchronously ...

@jstrecker and @adamfenn28: which OS X versions are each of you running? (I vaguely recall there being some differences in the way NSURLConnection handles timeouts between OS X releases.)

Regarding your question #2, no, you cannot force -execute::: to happen --- the QC engine orchestrates that. If you're doing the query on a background thread, store the result in an ivar, then set the output port's value in -execute::: next time QC invokes it for you.

adamfenn28's picture
Re: trouble setting output ports to data from asynchronously ...

I'm running Snow Leopard.

I changed my path's time mode to kQCPlugInTimeModeIdle. That seems to have solved the problem of the lag between triggered refreshed with kQCPlugInTimeModeNone, even though my patch is a processor patch. With this, I can see how I can make this work well with async, which does seem to fail very quickly when there's an issue.

jstrecker's picture
Re: trouble setting output ports to data from asynchronously ...

I'm also on Snow Leopard.

@adamfenn28 -- Here's a possible explanation of timeoutInterval, if that helps. (The quoted excerpt is not in Apple's online documentation, but is in NSURLRequest.h.) http://stackoverflow.com/questions/3563362/problem-with-nsurlconnection-...

@smokris -- http://fdiv.net/2007/07/10/nsurlconnection-sendsynchronousrequest-enforc...

adamfenn28's picture
Re: trouble setting output ports to data from asynchronously ...

I think I've pretty much settled on async for a few reasons. It's fails correctly with a network error, and it doesn't cause quite the stuttering of my animations that sync causes.

I have a small issue though. I can't seem to read ivar strings from within -execute:::. I can access int ivars just fine. I've attached a sample. What am I doing wrong?

Thank you for your help!

PreviewAttachmentSize
Archive.zip3.03 KB

adamfenn28's picture
Re: trouble setting output ports to data from asynchronously ...

It got it! It was the autorelease that was getting me. Super obvious!