Summarized using AI

Flattening Recursion with Fibers

Jamis Buck and Adviti Mishra • November 13, 2024 • Chicago, IL • Talk

The presentation titled Flattening Recursion with Fibers given by Jamis Buck and Adviti Mishra at RubyConf 2024 discusses the creative use of Ruby's fibers to solve a problem related to recursion in callbacks within MongoDB. The speakers share a narrative structured in three acts detailing their journey of overcoming a stack overflow issue caused by recursive callbacks in embedded documents.

Key Points of Discussion:

  • Initial Problem: The inherent challenge was that Ruby callbacks, especially around callbacks, are recursive in nature. Thus, when attempting to save a parent document with numerous embedded documents, the recursion could lead to a system stack error.
  • First Attempt: Ruby Doobie, the fictional developer utilized by the presenters, initially tried to handle callbacks through a naive recursive approach but quickly encountered stack overflow issues when the number of embedded documents exceeded a certain limit.
  • Fibers Introduced: The innovation came when Ruby Doobie explored the use of fibers, which are a concurrency primitive that allows for pausing and resuming code execution. The presenters illustrated how fibers can help manage the callbacks without growing the stack.
    • Example with Fibers: They conceptualized using fibers to flatten recursion, providing an example of creating a separate fiber for each function call, allowing the code to yield and pause operations effectively without exhausting the stack.
  • Implementation in MongoDB: The solution transitioned to MongoDB, where they harnessed the power of fibers to handle callbacks for each child document, yielding control efficiently between around callbacks, thus avoiding the stack overflow.
  • Conclusion: The successful implementation allowed handling a vast number of embedded documents without running into stack space issues. The villagers in their story could have as many children (or documents) as desired without fear of errors, showcasing a successful outcome with a unique solution.

Main Takeaways:

  • The effective use of Ruby fibers can alleviate stack overflow issues caused by traditional recursive callback patterns.
  • By understanding the capabilities of fibers, developers can creatively address problems in their code that may not seem initially related to concurrency.
  • The narrative wrapped around the technical explanation added an engaging layer and emphasized teamwork and iterative problem-solving in software development.

Flattening Recursion with Fibers
Jamis Buck and Adviti Mishra • November 13, 2024 • Chicago, IL • Talk

We used Ruby's fibers at MongoDB to unwind a recursive algorithm and execute it horizontally, without the stack overhead of recursion.

"Hang on," you might be thinking. "I thought fibers were a concurrency primitive?"

You're not wrong!

In this presentation, we'll summarize the problem we encountered while working with recursive callbacks, and give an overview of fibers in Ruby. Then, we'll put the two together and show you exactly how we ended up using fibers to solve an issue that was completely unrelated to concurrency.

RubyConf 2024

00:00:15.320 welcome we're so excited to have you
00:00:17.920 here with us for this talk let me give
00:00:20.840 this to AB thank you we're so excited to
00:00:23.920 have you here we're really excited about
00:00:25.880 this topic uh thank you to Jenny for the
00:00:28.000 introduction again I'm jamus buck um at
00:00:30.359 mongodb I've been at mongodb for almost
00:00:33.480 two years and I've been working with
00:00:35.040 Ruby professionally for about
00:00:37.360 20 hello I'm Aditi I've been working
00:00:40.280 with Ruby professionally for two months
00:00:42.440 which is not as much as 20 years uh but
00:00:45.960 I learned Ruby this past summer while
00:00:47.680 interning at mongodb while working on
00:00:50.079 this project flattening recushion with
00:00:51.879 fibos and I'm super excited to present
00:00:53.760 this with
00:00:54.640 jamus wonderful thank you thank you
00:01:01.120 so for about the next 20 minutes or so
00:01:04.280 we will be regaling you with a tale in
00:01:07.479 three acts of success and failure hope
00:01:11.880 and despair and ultimate Victory so
00:01:15.360 buckle your seat belts and hang on
00:01:16.960 because it's going to be a little bit of
00:01:18.600 a roller
00:01:19.680 coaster so let's start with our First
00:01:22.400 Act amongst our one index list of Acts
00:01:25.000 act one in which our hero encounters a
00:01:28.000 monster Once Upon a Time there was a
00:01:30.520 village of happy villagers some of the
00:01:33.479 villagers like this one in the middle
00:01:36.119 had four
00:01:37.320 children they wanted to live in an
00:01:39.439 egalitarian society where whenever the
00:01:42.520 children sorry whenever the parents
00:01:44.640 Prosper the children should have the
00:01:46.079 right to prosper as well in order to
00:01:48.360 codify their laws they hire Ruby doobie
00:01:51.799 a friendly neighborhood Ruby
00:01:56.079 developable so Ruby doobie is a bit of a
00:01:59.439 practical pragmatic fellow uh rather
00:02:02.759 than inventing everything from scratch
00:02:04.520 he reached for what was familiar and
00:02:06.560 already present and so he decided to use
00:02:09.599 active model as the basis for his
00:02:12.239 framework uh so this will look very
00:02:14.200 familiar to uh rails
00:02:17.519 programmers uh active model was mature
00:02:20.840 it's familiar and it does almost
00:02:23.480 everything that he needs one of the
00:02:25.519 things it did not do is it doesn't
00:02:28.120 support embedded documents embedded
00:02:31.800 documents are records that are actually
00:02:33.560 physically nested inside of other
00:02:35.360 records has anyone here played with
00:02:37.120 mongodb before raise of hands a few of
00:02:39.920 you that's awesome okay so those of you
00:02:41.599 who raise your hands you might be
00:02:43.040 familiar with embedded documents but
00:02:45.519 that is one of the things that uh Ruby
00:02:47.879 doie needed in his framework and so he
00:02:50.920 he knew he was going to have to
00:02:51.879 implement a little bit extra honestly
00:02:54.680 active model still did almost everything
00:02:56.680 that he needed the biggest special case
00:02:59.040 that he encountered was with callbacks
00:03:01.200 because the callbacks on these embedded
00:03:02.840 records there's there's a thing called
00:03:04.640 cascading callbacks which is a situation
00:03:07.799 where when an operation hits or when an
00:03:11.000 operation happens on the parent you want
00:03:13.799 the uh the callbacks to filter down
00:03:16.959 through to the children as well so in
00:03:18.760 this example when the parent is saved
00:03:21.680 Ruby doobie wants this do something call
00:03:24.040 back to also be invoked on each of the
00:03:26.120 children as
00:03:28.760 well so that's not too bad that wasn't
00:03:31.439 too bad but around callbacks make this a
00:03:33.840 little more complicated because they're
00:03:36.000 recursive in nature right you can't just
00:03:38.000 call them in sequence you have to Nest
00:03:40.040 them together and that gets a little
00:03:41.560 tricky if you're not familiar I suppose
00:03:43.959 most of you probably are but if you're
00:03:45.239 not familiar with around callbacks the
00:03:47.239 idea is that when something happens like
00:03:49.920 a save this around call back is called
00:03:53.280 and begins executing and then that yield
00:03:56.159 in the middle returns control back to
00:03:59.239 the calling operation so that it can
00:04:01.400 finish so for instance saving and when
00:04:04.079 the save is finished it comes back to
00:04:06.159 this method and completes so it's not as
00:04:09.040 straightforward as a simple before and
00:04:11.519 after call
00:04:14.879 back so this is Ruby Do's first attempt
00:04:18.680 right he's gonna we're going to deal
00:04:20.320 with just save for now but given a save
00:04:22.440 operation he's going to call this run
00:04:24.600 embedded callbacks method and it's going
00:04:27.800 to take a block that's going to encap
00:04:29.840 capsulate the the coree functionality of
00:04:32.160 the save in this case that really
00:04:34.400 actually save method is going to save
00:04:36.960 the top level record in this hierarchy
00:04:39.800 of embedded documents so run embedded
00:04:42.680 callbacks itself his first naive attempt
00:04:45.280 it just iterates over all of the
00:04:47.199 children in the list calls run callbacks
00:04:49.840 on each of them and yields to the
00:04:53.120 block except that's that's really not
00:04:55.720 what he wants for two
00:04:57.759 reasons the first reason
00:05:00.199 is that if there are no children nothing
00:05:03.360 happens right if there's no children
00:05:05.400 that block has never yielded to the
00:05:07.400 parent has never persisted chaos Reigns
00:05:10.280 everything falls apart world ends
00:05:12.759 Etc also because this is happening for
00:05:16.400 each child he's now invoking that block
00:05:19.759 if there's if there's 10 children he's
00:05:21.280 invoking that block 10 times there's
00:05:23.800 saving the top level document 10 times
00:05:25.800 and that is definitely not the intent so
00:05:29.479 yes to go back to the drawing board a
00:05:30.840 little bit this is this is what he came
00:05:33.520 up with then this is the recursive
00:05:35.880 implementation anyone that's done much
00:05:38.319 with like lisp or any of those uh
00:05:40.840 functional languages will be familiar
00:05:42.360 with this uh idiom for iterating over a
00:05:45.720 list if the list is empty if there's no
00:05:48.160 children he just immediately yields and
00:05:50.600 returns that ensures that the the meat
00:05:53.440 of the operation is accomplished
00:05:55.199 regardless of how many children there
00:05:56.560 are then he splits the list he takes the
00:06:00.120 head which is the first element of the
00:06:01.680 list and the tail which is the rest and
00:06:03.960 he runs the callbacks on the
00:06:06.080 head within that block that block is the
00:06:09.280 piece that will be invoked between
00:06:11.160 before and after callbacks and in the
00:06:13.280 middle of around
00:06:14.759 callbacks So within that block he's
00:06:17.120 going to check first to see are there
00:06:18.680 any more elements in the list or is the
00:06:20.560 tail empty if the Tail's empty that
00:06:22.639 means the list has finished processing
00:06:24.759 and there's nothing left to do so we
00:06:27.479 yield and that's the end
00:06:30.280 if there's more elements in that list if
00:06:32.560 the tail is not empty we're going to run
00:06:35.400 embedded callbacks again recursively on
00:06:38.199 the tail to ensure that everything gets
00:06:41.080 done so I'm going to pause for just a
00:06:43.160 second make sure that you can convince
00:06:46.280 yourselves that this truly does cover
00:06:48.880 the entire list and that it's recursive
00:06:51.120 does anyone have any questions right now
00:06:54.479 about what we're doing here
00:07:02.520 okay the hardest thing about public
00:07:04.160 speaking when you're asking a question
00:07:06.400 is the
00:07:07.720 waiting I've heard you're supposed to
00:07:09.639 count to seven after you ask a question
00:07:12.000 I tell you that is the longest seven
00:07:13.960 seconds you will ever
00:07:18.360 experience so it works Ruby doie takes a
00:07:22.280 deep breath sigh of relief basks in the
00:07:26.120 glow of the contentment of a job well
00:07:28.240 done watches as the villagers and the
00:07:30.560 children Prosper together life is
00:07:34.680 good inspired by Ruby Do's work one of
00:07:37.759 the villagers decides to adopt 1,000
00:07:40.280 children there's always that one guy I
00:07:43.599 know what you're thinking that's a lot
00:07:45.319 of
00:07:46.400 children surprisingly what happens is
00:07:49.080 that the villagers are encountered by a
00:07:51.280 terrible system stack error that arises
00:07:54.039 from the depths of the call stack
00:07:55.720 threatens the village and makes Ruby
00:07:57.759 doobie sweat and anxiety
00:08:00.840 now I would encourage all of you to take
00:08:02.400 a minute to think about why the system
00:08:04.479 stack error occurs or if you don't want
00:08:07.039 to think about that you can also
00:08:08.759 appreciate the jokes that I came up with
00:08:10.440 on the
00:08:13.599 slide here's the implementation as a
00:08:16.080 refresher and like jamus I'm also going
00:08:18.400 to count seven Mississippi
00:08:33.080 okay um as jamus explained this is a
00:08:36.200 recursive function where run embedded
00:08:38.320 call backs is called for each and every
00:08:39.959 child document when you have a lot of
00:08:42.760 child documents what happens is that the
00:08:44.800 call Stacks add up and lead to the stack
00:08:47.399 space being exhausted even though this
00:08:49.720 is technically not a case of infinite
00:08:53.640 recursion so with the system stack error
00:08:56.760 Beast looming over the village Ruby doie
00:08:59.959 desperately tries to build a wall to
00:09:01.640 keep it out to protect the villagers
00:09:04.360 he's building this wall he's panicking
00:09:06.839 children are screaming villagers are
00:09:08.839 screaming crying running mad running
00:09:11.399 madly around and all that Ruby Dobie can
00:09:14.760 think is how did everything go so
00:09:19.800 wrong we encountered this very situation
00:09:22.959 well minus the screaming villagers in
00:09:25.760 mongoid okay which is our active record
00:09:28.440 for mongodb
00:09:30.480 um with the embedded
00:09:32.279 children one of our users somehow had
00:09:35.560 more than a thousand embedded records
00:09:37.160 and a single record with callbacks with
00:09:40.440 around call actually it wasn't even with
00:09:42.240 around anyway with with callbacks and it
00:09:44.399 caused the stack to explode and so we
00:09:47.760 had to go and investigate that figure
00:09:49.920 out what was going on and ultimately
00:09:52.000 instead of a nice clean
00:09:53.560 solution and we we all know what it's
00:09:55.640 like to be under pressure with other
00:09:57.519 priorities we didn't have the time or
00:10:00.040 the resources available to research this
00:10:01.720 so we had to just backlog it we
00:10:04.200 implemented a flag that basically
00:10:06.440 disabled around callbacks for embedded
00:10:08.480 children not an elegant solution Ruby
00:10:11.320 doobie is obviously not happy with it
00:10:13.360 you can tell by that little Grimace on
00:10:15.680 his face this is not elegant the
00:10:18.000 villagers are safe the children are safe
00:10:21.160 but they've sacrificed some of their
00:10:23.160 freedoms
00:10:24.720 and the parents and the children can no
00:10:27.279 longer be guaranteed to prosper
00:10:32.160 equally the lack of time and resources
00:10:34.600 that jamus mentioned were provided by me
00:10:36.560 and intoo in this past summer dealing
00:10:38.600 with this project um but here's act two
00:10:41.720 in which our hero embarks on the side
00:10:43.639 quest to linearize the problematic
00:10:45.639 recursive callstack which is a really
00:10:47.360 long way of saying recursion doesn't
00:10:49.680 work fix
00:10:52.959 it on a quest to find an alternate
00:10:56.000 implementation Ruby duie encounters a
00:10:58.560 mysterious artifact made of long fibers
00:11:01.560 never forged for this specific battle
00:11:03.639 but carrying great hope with it inspired
00:11:06.399 to learn as much as he could about this
00:11:08.839 mysterious artifact he reads A
00:11:11.160 mysterious scripture accompanying it
00:11:13.320 titled dogs. Ruby
00:11:17.440 high.org while studying the scriptures
00:11:19.800 he learns that fibos are a concurrency
00:11:22.440 primitive where the programmers have
00:11:24.279 control over when a fiber can be paused
00:11:27.399 or resumed the fiber. new method is used
00:11:30.560 to instantiate a fiber with a code block
00:11:33.480 while the fiber. resume method is used
00:11:35.839 to resume the execution of the code
00:11:38.160 block that the fiber was instantiated
00:11:40.279 with in this example we create a fiber
00:11:43.399 instantiated with the code block that
00:11:45.279 prints dare we hope and calling fiber.
00:11:48.480 resume resumes the execution of this
00:11:50.839 code block thus printing dare we hope
00:11:55.000 for a more substantial example here we
00:11:57.440 create a fiber that's instantiated with
00:11:59.440 a code block that prints start calls
00:12:02.519 fiber. yield and prints finish the
00:12:05.320 fiber. yield method can be used to pause
00:12:07.760 the execution of a
00:12:09.519 block while we do some other work and
00:12:11.880 then resume the execution of the block
00:12:13.800 later on kind of like an around filter
00:12:16.680 so in this example the first fiber.
00:12:18.920 resume starts executing the code block
00:12:21.399 that prints start when fiber. yield is
00:12:24.120 encountered control is returned back to
00:12:27.120 the second fiber. resume the that again
00:12:29.600 resumes the execution of the code block
00:12:31.800 printing
00:12:33.720 finish now let's consider let's consider
00:12:36.600 even more involed example over here we
00:12:39.360 have four different functions Fu bar baz
00:12:42.399 and bank here we're creating four levels
00:12:45.079 of nested blocks and what happens is
00:12:47.399 that when fu is invoked it prints enter
00:12:50.000 Fu the yield statement yields to the
00:12:52.680 code block pass into it that calls bar
00:12:55.760 printing enter bar and so on so forth
00:12:58.440 until done is printed after that exit
00:13:02.000 bang Exit B exit bar and exit Fu are
00:13:05.079 printed these nested blocks simulate the
00:13:08.320 nested nature of the recursion that Ruby
00:13:11.000 doie encountered and his main goal was
00:13:13.920 to try and linearize this somehow using
00:13:17.639 fibos so what ruby doie did was that he
00:13:20.800 created a fibber for each and every
00:13:22.760 function and that's what the fibers
00:13:25.160 equals whatever do map function does um
00:13:28.839 it essentially creates a fiber for Fu
00:13:31.000 bar baz and bang where the code block
00:13:33.760 simply invokes the function passing into
00:13:36.440 it a code block that calls fiber. yield
00:13:39.519 so what fibers. each resume does is that
00:13:42.040 it first resumes the fiber for the fu
00:13:44.360 function that invokes Fu printing enter
00:13:47.680 Fu and once Fu yields to the code block
00:13:51.040 fiber. yield is invoked which returns
00:13:53.519 control to the fibers. each line that
00:13:56.240 goes on to do the same thing for bar
00:13:58.839 back and bang thus printing enter Fu
00:14:01.759 enter bar enter baz enter bang then puts
00:14:05.440 done is encountered which prints done
00:14:07.920 fibers are reverse each resume similarly
00:14:10.839 resumes all the fibers but from bang all
00:14:13.560 the way back to Fu thus printing exit
00:14:16.600 bang exit baz exit bar and exit Fu wow
00:14:20.720 that was difficult to say um and as you
00:14:23.639 can see the output on this slide was the
00:14:26.000 same as the output on the previous slide
00:14:28.440 and Ruby we kind of linearized the
00:14:31.399 recursive nature of nessed
00:14:34.480 blocks and the key ingredient was
00:14:36.920 obviously the fiber. yield that was
00:14:38.959 passed into the function that was
00:14:41.199 invoked for each of the four functions
00:14:43.800 um once again I'm going to pause for
00:14:46.680 maybe a minute for any questions you may
00:14:48.800 have um because we know this is a lot to
00:14:51.839 digest I did this over the course of two
00:14:54.160 months we have 20 minutes with you all
00:15:00.560 yes yes question right here hold on one
00:15:02.199 second let's get a mic to
00:15:06.680 you so that this approach linearizes
00:15:11.000 the um it linearizes the recursion but
00:15:14.320 it doesn't fix the problem it doesn't
00:15:16.920 fix the problem it does it not fix the
00:15:18.480 problem yeah because it sounds it
00:15:20.000 doesn't look like this would avoid the
00:15:21.920 stack space error that's the thing is
00:15:25.519 the fibers are no longer created on the
00:15:27.399 stack you're right there's still memory
00:15:29.519 use the use is still the same in fact
00:15:32.800 but now the the it's being put on the
00:15:35.079 Heap as separate fibers as opposed to
00:15:37.639 being pushed onto a single stack okay
00:15:40.079 good point good point that's a great
00:15:42.800 question thank
00:15:44.680 you uh one more question over here oh
00:15:51.959 sorry what about the previous recursion
00:15:54.600 was keeping something on the stack I
00:15:56.079 didn't understand that bit like like you
00:15:58.360 weren't assuming in place right so you
00:16:00.360 must have been keeping something on the
00:16:02.519 stack so you're asking how no go to the
00:16:07.000 the Fubar baz n in the original problem
00:16:10.759 Oh not here in the original one where
00:16:12.880 you were trying to save the children I
00:16:16.120 didn't understand because it it looked
00:16:17.279 to me like you were doing their cursive
00:16:18.480 call at the end and so naively I'm
00:16:19.920 trying to think of like what's being
00:16:21.120 kept on the stack that's
00:16:22.959 not go forward to the solution right
00:16:26.040 there y that one yes yeah yeah yeah
00:16:29.680 okay so yeah the key to the recursion is
00:16:32.360 that call inside the block to run
00:16:34.880 embedded
00:16:36.040 callbacks so it's calling itself and
00:16:38.480 every time it calls itself it pushes a
00:16:40.360 frame onto the stack to to keep track of
00:16:43.639 where it was when it needs to return
00:16:45.040 from that function does that make sense
00:16:47.959 yes okay and so if we have a thousand
00:16:50.519 children it's going to push onto that
00:16:53.040 stack each time we recurse on that okay
00:16:57.079 okay fair enough does that make sense am
00:16:59.160 I misunderstanding the question no you
00:17:00.959 you answered it I have more questions
00:17:02.600 but uh I'll follow up with them later or
00:17:04.160 something okay we'll do more questions
00:17:05.439 at the end of the presentation as well
00:17:07.439 okay great thank
00:17:15.640 you right there okay
00:17:18.280 so things are looking pretty good this
00:17:21.880 this recursive algorithm has been
00:17:24.120 flattened to a linear algorithm we've
00:17:27.880 taken care of the problem with the stack
00:17:29.720 space being exhausted it's all being put
00:17:31.720 on the Heap now so theoretically we can
00:17:35.280 go for as as deeply as we need to
00:17:39.520 without worrying about exhausting the
00:17:40.919 stack because the stack is no longer
00:17:43.200 being consumed for each child things
00:17:45.880 look good
00:17:48.440 unfortunately in was it in in theory
00:17:51.480 there's no difference between theory and
00:17:53.120 practice but in practice there
00:17:55.640 is um so Ruby does is armed with this
00:17:59.000 new weapon he's he's tried it out in a
00:18:02.440 sandbox and it looks really promising
00:18:04.400 but now here he is standing in front of
00:18:06.200 this Beast armed with this new weapon
00:18:09.320 and he's like where do I start what am I
00:18:12.480 supposed to do does does he need to put
00:18:17.000 this in active record the Callback
00:18:19.240 system if so where like I don't know if
00:18:22.159 any of you have actually looked at
00:18:24.159 active support callbacks but there's a
00:18:26.000 lot of code there and it's non-trivial
00:18:28.559 like it does a lot of optimizations
00:18:30.440 there's a lot of different pieces does
00:18:32.120 this need to go in call template method
00:18:34.000 call does this need to go in callbacks
00:18:36.240 run callbacks does it belong in rails at
00:18:39.440 all because again this was intended to
00:18:42.240 fix the problem with um embedded
00:18:45.679 children embedded documents which is not
00:18:47.760 something that active record even
00:18:49.600 supports so if it doesn't belong in
00:18:51.840 rails do we extract this as a plugin but
00:18:55.159 that still leaves us with the same
00:18:56.600 problem right even if it's a plugin we
00:18:58.400 still need to figure out what we patch
00:19:00.520 and where it
00:19:02.080 goes um it's terrible our hero is faced
00:19:06.360 with this new bug bear of complexity
00:19:09.320 that has snatched Victory right at the
00:19:11.880 moment when he felt like it was
00:19:13.960 there and he
00:19:16.080 desaires as someone who loaned Ruby and
00:19:18.480 Ruby own rails for two weeks before
00:19:20.159 diving into this project I can relate
00:19:22.320 with the fact that the hero did despair
00:19:24.080 a
00:19:25.480 lot and that brings us to act three in
00:19:28.159 which the Beast is finally vanquished
00:19:30.760 the hero's Melancholy was bottomless he
00:19:33.440 decides to go on the walk on the other
00:19:35.320 side of the village whose Hill looks
00:19:37.679 exactly like the hill on the first few
00:19:39.880 slides but symmetrically opposite
00:19:41.600 because again I'm in computer science
00:19:43.240 I'm not an
00:19:45.080 artist while going on this walk and
00:19:47.200 touching much needed grass he is struck
00:19:49.320 with the lightning of inspiration and he
00:19:51.840 realizes instead of touching rails why
00:19:54.720 don't we just drop fiber. yield within
00:19:57.200 mongoid itself
00:19:59.280 so this is what ruby duie came up with
00:20:02.000 what he does is that for each child
00:20:03.640 document he creates a fibber it's
00:20:05.799 instantiated with a code block that
00:20:07.880 calls run call backs on that child
00:20:09.880 document passing into it fiber. yield if
00:20:13.440 you make comparisons with the Fubar baz
00:20:15.799 example earlier this is structurally
00:20:18.320 similar to that what the fibers. each
00:20:21.400 resume line does is again it
00:20:23.559 sequentially goes through the fibos of
00:20:25.520 all the children all the child documents
00:20:28.400 and calls run call backs on it so for
00:20:30.960 the first child document the Run call
00:20:33.120 backs would run the before call backs
00:20:35.520 and the first part of the around
00:20:38.080 callbacks do that for all the children
00:20:40.320 in the list then yield to the code block
00:20:43.600 pass into run embedded callbacks and
00:20:46.240 then do the same thing in RoR so it
00:20:49.000 would for the last child execute it the
00:20:52.440 second half of its around call backs and
00:20:54.400 the after call backs all the way up to
00:20:56.480 the first child
00:21:00.840 and
00:21:01.720 miraculously it works what a wild crazy
00:21:05.760 idea something like it's not even
00:21:07.960 intended for this purpose it's a
00:21:09.400 concurrency primitive right and yet here
00:21:12.360 it actually works and our hero takes a
00:21:15.520 deep breath of relief
00:21:18.200 feels miraculously saved go ahead the
00:21:22.279 next one oh I don't know what Happ what
00:21:25.520 did you do
00:21:27.000 okay I got dad
00:21:30.480 thiso and now the villagers can have as
00:21:33.880 many children as they want right a
00:21:35.960 thousand 10,000 the only limit now is
00:21:38.919 the size of their house right or maybe
00:21:41.520 the village or whatever um but Ruby doie
00:21:44.880 gleefully tears down that wall the
00:21:47.360 villagers are now free to run about
00:21:49.799 they've regained all of their freedom
00:21:51.600 parents and children are able to prosper
00:21:54.320 together there's feasting and fireworks
00:21:57.039 there's celebration dance in the streets
00:22:00.279 everything is great and life is good and
00:22:02.760 they live happily ever after until the
00:22:06.039 next Beast eventually rears its ugly
00:22:08.520 head because as we all know there's
00:22:10.480 always another
00:22:12.120 Beast so thank you very
00:22:20.720 much find us off to Bo for free socks
00:22:23.480 and stickers the socks are really nice
00:22:26.320 we're wearing them right now um yeah and
00:22:29.279 we love to answer any questions you may
00:22:31.360 have you can also chat with us
00:22:36.080 thereafter yes yes question over
00:22:42.880 here so at a meta level this is
00:22:46.640 specifically an issue where you have
00:22:50.240 code that you need to call that you
00:22:51.480 don't control that yields in the middle
00:22:53.360 of it and that's the reason why so like
00:22:55.360 for example for like a before callbacks
00:22:57.200 thing this would be a non you could
00:22:59.080 stick lambdas around it and be fine um
00:23:01.760 so this is specifically for you need to
00:23:04.480 pass it a block and fibers allow you to
00:23:06.840 pause at any point in time in the middle
00:23:08.360 of the execution and then resume it
00:23:09.919 later and that's why you're using fibers
00:23:11.760 here right that is correct that was
00:23:13.960 almost not a question but you saved
00:23:15.480 yourself at the very end okay cool I
00:23:18.360 just wanted to make sure that I like
00:23:19.960 understood the the use of this pretty
00:23:23.120 cool use it's pretty Niche but it was
00:23:25.240 exactly what we needed here cool
00:23:31.000 we have two questions oh two questions
00:23:33.960 one and two arm wrestle wow I feel both
00:23:36.000 of those for
00:23:38.480 once I I a quick question was it is it
00:23:42.240 is it something about fibers or is it
00:23:44.919 just that fibers give you that ability
00:23:47.720 to um resume twice and and you know or
00:23:51.440 is it anything specific to fibers
00:23:54.200 honestly in this case it was just that
00:23:56.039 it allowed us to interrupt in the middle
00:23:59.320 and then resume them on our own schedule
00:24:01.960 so yes it it it could have been anything
00:24:04.320 it just happened to be a concurrency
00:24:05.919 primitive that did the job for
00:24:08.440 us yeah and I think like around call
00:24:11.320 callbacks is what makes us more
00:24:13.120 complicated because around callbacks
00:24:15.000 require like your run call backs to
00:24:17.120 yield to the code block pass in um and
00:24:19.640 so on so
00:24:21.000 forth that's a great
00:24:24.799 question the the uh the initial problem
00:24:28.520 statement looked like it could have been
00:24:30.640 solved by iterating over the
00:24:33.960 children um you want back I didn't see
00:24:37.399 in the in that original one code that
00:24:40.480 needed to be executed before and after
00:24:43.320 okay so yeah let's get back to that
00:24:45.720 statement
00:24:46.679 here uh this one this was the this was
00:24:49.520 the his this was Ruby doob's first try
00:24:51.919 iterating over the children right so
00:24:54.080 there were two problems
00:24:56.000 here the next one oh the next one next
00:24:58.600 one okay yes okay so again what's the uh
00:25:04.919 can you restate your question about this
00:25:06.200 one okay oh sorry
00:25:10.440 um this
00:25:12.880 looks it what would happen if you
00:25:15.919 iterated over each of the
00:25:19.840 children running
00:25:22.039 callbacks and then
00:25:24.240 yielding so if we go back to the
00:25:26.559 previous slide
00:25:29.760 oops we're chasing each other here ah
00:25:33.039 okay you go it I'll just do it okay so
00:25:37.240 that's kind of what we're doing
00:25:38.799 here but we can't yield outside of that
00:25:41.600 iteration because run callbacks has a
00:25:44.520 block that yields that's block the
00:25:46.840 constraint is the rails API that we're
00:25:49.240 dealing with here that we don't have a
00:25:50.679 lot of control over because the rails
00:25:52.799 API needs to support around callbacks
00:25:55.919 which are yield based that was the part
00:25:58.559 I was missing thank you that makes sense
00:25:59.799 okay great great uh right behind you
00:26:03.559 Jenny I'm curious about the creative
00:26:06.640 process behind arriving at this solution
00:26:09.840 um because it's so infrequently the case
00:26:12.240 that you know you you take a primitive
00:26:15.799 with a very that's like very
00:26:17.360 purpose-built and using it in different
00:26:19.360 application is like that's often a
00:26:20.919 disaster in this case I would say it's
00:26:23.279 you know completely the right choice so
00:26:25.840 how do you unlock your thinking and
00:26:27.720 reach
00:26:28.760 you know this right solution rarely the
00:26:32.480 case Jam do you want to talk about like
00:26:34.200 the proof of concept and then I can talk
00:26:36.000 about how it fit in perfect that's just
00:26:37.880 what I was thinking too so there were
00:26:39.279 two parts to this before adidi came on
00:26:41.720 for her internship I kind of had the
00:26:44.440 brainstorm that fibers might work and it
00:26:48.880 happened because I've played with fibers
00:26:51.840 I
00:26:52.480 knew conceptually what they were and how
00:26:55.200 they worked and I knew that fiber yield
00:26:57.039 basically let you pause in the middle
00:26:59.880 and so taking that knowledge and after
00:27:03.559 thinking deeply about the problem that
00:27:05.159 we had where I was like we need to
00:27:06.960 somehow linearize linearize this right
00:27:09.799 like remove the recursive aspects so
00:27:12.120 that we don't recurse and overflow the
00:27:15.080 stack and so as I was thinking about it
00:27:17.320 I was like I wish there was some way as
00:27:19.360 we're executing this to stop and save it
00:27:23.360 so that we can then put it aside and
00:27:24.840 then do the same thing for the next one
00:27:26.799 right so you're like going this far
00:27:28.559 putting it aside this far putting it
00:27:30.120 aside and that's where I was like well
00:27:31.840 that's kind of like stopping a block in
00:27:33.440 the middle which isn't that what fibers
00:27:35.520 do and so I wrote a simple proof of
00:27:38.799 concept which is equivalent to Ruby
00:27:41.039 doob's like wielding his weapon in
00:27:44.039 practice uh in theory um and it it
00:27:47.519 seemed to have promise it seemed like it
00:27:49.120 would work but again I didn't I didn't
00:27:51.960 have the time or the space to actually
00:27:54.200 work on it I had to
00:27:55.880 stop and then a video came for her
00:27:58.760 internship and I was like H I know what
00:28:01.799 we can make her
00:28:04.080 do um yeah working on this project was
00:28:06.399 incredibly fun um the way that I
00:28:08.399 approached it was I obviously was
00:28:10.240 exposed to Ruby for the first time so I
00:28:11.919 tried to get on boarded but then I tried
00:28:14.399 to understand how callbacks worked at
00:28:16.799 the level of active support in rails so
00:28:18.799 I went through like the entire file
00:28:20.440 function by function trying to
00:28:22.480 understand like key factors behind the
00:28:24.760 decision- making process behind the
00:28:26.279 design process and I did the same for
00:28:28.159 for mongoid as well and the and the key
00:28:31.480 part they had to figure out was where
00:28:33.120 exactly could fibos play a part it could
00:28:35.600 either play aart at some point within
00:28:37.880 the um within like the call back
00:28:40.399 implementation of Act of support or
00:28:42.320 within mongoid itself uh but jamus and I
00:28:44.640 were pretty confident that it would most
00:28:46.200 probably be in Ruby on Rails so for each
00:28:48.480 and every point I came up with like
00:28:50.039 multiple proofs of proof of Concepts
00:28:51.840 tried to figure it out on pen and paper
00:28:53.399 if it worked and ultimately I went all
00:28:55.480 the way up to mongoid and then realized
00:28:57.880 that solution lay there all along so it
00:29:00.080 was a very iterative process um and
00:29:03.120 another thing that helped me was taking
00:29:05.120 this leap of faith that we do that we
00:29:07.399 take in recursion where you trust that
00:29:09.200 calling the function Returns the right
00:29:10.720 answer I kind of trusted that Ruby on
00:29:13.440 Rails was implemented as efficiently as
00:29:16.000 possible given that the file was like
00:29:18.240 touched around 12 to 13 years ago um and
00:29:22.519 then we just like used whatever they had
00:29:24.919 and made changes on our end which let a
00:29:27.360 solution be right concise and she
00:29:29.720 actually touched grass to make to figure
00:29:31.679 it out so so I'm getting the signal
00:29:33.919 we're done thank you so much if you have
00:29:35.760 more questions please do find us
00:29:37.120 afterward and also please do come find
00:29:38.919 us for some socks and stickers thank you
00:29:40.799 very
00:29:42.360 much thank you
Explore all talks recorded at RubyConf 2024
+64