Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scheduler improvements #150

Closed
wants to merge 48 commits into from
Closed

Scheduler improvements #150

wants to merge 48 commits into from

Conversation

joshaber
Copy link
Member

A start on solving #94, #136.

Alright so here's an attempt at fleshing out RACScheduler.

It does a couple things:

  • Adds +currentScheduler.
  • Only supports using queues that we own.

My current thinking is that all subscriptions would use the +subscriptionScheduler so that we can ensure that by the time we're in a didSubscribe block, we can determine the +currentScheduler.

This would enable us to fix #94 by deferring the re-subscription on the current scheduler.
It supports #136 by making concurrency explicit with the option of creating a serialized scheduler from a concurrent scheduler.

So where are the holes in this? Does this really address the problems? What sorts of crazy edge cases do we need to test?

@ghost ghost assigned jspahrsummers Nov 29, 2012
@synchronized(scheduler) {
NSAssert(scheduler.queue != NULL, @"+concurrentQueueScheduler used without being in a scheduler.");

NSMutableArray *queue = (__bridge id)dispatch_get_specific(RACSchedulerImmediateSchedulerQueueKey);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dispatch_get_specific / dispatch_queue_set_specific require a GCD dispatch queue, would -[NSThread threadDictionary] work better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Coneko GCD queues aren't guaranteed to be pinned to a specific thread. It may change between the execution of blocks, so any thread-local storage is usually a bad idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the weird semantics of this scheduler is to guarantee that it never yields control while executing a scheduled block.

If it's called from a serial queue, if a block is currently executing it's going to be on the same thread. If it's called from a concurrent queue it's unpredictability doesn't change how you would treat a concurrent queue.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Coneko Good point. @jspahrsummers 👍 ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think what I said in https://github.com/github/ReactiveCocoa/issues/94#issuecomment-10820187 is going to be an issue. Maybe we should continue that discussion at the top level here.

@joshaber
Copy link
Member Author

I still think what I said in #94 is going to be an issue. Maybe we should continue that discussion at the top level here.

@jspahrsummers Could you expand on that a bit more?

@jspahrsummers
Copy link
Member

Well, for instance, I was very close to implementing RACSubscriber with a serial GCD queue instead of @synchronized (and would've, if it weren't for how generally shitty GCD can be), and using dispatch_sync throughout it to serialize received messages.

I imagine that similar patterns are common in RAC consumers, and, with this implementation, any dispatch_sync would:

+deferredScheduler suffers from the same problem, since it's basically the current scheduler with some additional behavior.

@joshaber
Copy link
Member Author

@jspahrsummers I'm not sure I follow. This proposed implementation doesn't support dispatch_sync nor does it expose the queue directly.

@jspahrsummers
Copy link
Member

Sorry, I mean a dispatch_sync in application code from a RACScheduler to any GCD queue (created by them or global).

@joshaber
Copy link
Member Author

Ah yeah, but I think that's ok-ish. We have to make the strong claim that schedulers are only valid when used with schedulers. They break as soon as you intermingle them with GCD or NSOperationQueue.

Maybe that's too inflexible, but I'd be curious to hear a case where it would be.

@jspahrsummers
Copy link
Member

Maybe we should back up a little bit.

Why does RACScheduler even need to exist in the first place? Is it just for the concept of an immediateScheduler? What can schedulers do that operation queues can't?


// These properties should only be accessed while synchronized on self.
@property (nonatomic, readonly, strong) NSMutableArray *disposables;
@property (nonatomic, assign) BOOL disposed;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have a getter = isDisposed.


// A scheduler that executes blocks in the current scheduler, after any blocks
// already scheduled have completed. If the current scheduler cannot be
// determined, it uses the main queue scheduler.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*+mainThreadScheduler

- (void)schedule:(void (^)(void))block {
NSParameterAssert(block != NULL);

NSMutableArray *queue = NSThread.currentThread.threadDictionary[RACIterativeSchedulerQueue];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't we use a serial GCD queue + queue-specific data to represent this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't manually run a GCD queue, so we can't guarantee when the enqueued blocks are executed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which part of the interface is that guaranteeing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I'll clarify the docs that they'll be called immediately after the current block.

return [RACScheduler schedulerWithScheduleBlock:^(void (^block)(void)) {
[queue addOperationWithBlock:block];
}];
+ (BOOL)onMainThread {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about +isOnMainThread?

@joshaber
Copy link
Member Author

joshaber commented Dec 1, 2012

✒️ 🙏


[NSThread.currentThread.threadDictionary removeObjectForKey:RACIterativeSchedulerQueue];
} else {
[queue addObject:block];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block should be manually copied.

@jspahrsummers
Copy link
Member

Sorry, one additional note about block management.

@joshaber
Copy link
Member Author

joshaber commented Dec 1, 2012

:bowtie:

andersio pushed a commit that referenced this pull request Sep 22, 2016
Add support for Xcode 8 and Swift 2.3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

-repeat doesn't stop after being disposed of
3 participants