Today's the last lecture of our midterm week. So what we'll do today is go over the solutions to Wednesday's exam. I have a chance to sort of talk about some of these things together. We haven't started grading the exams yet. So if we uncover problems today, then they can be incorporated into the grading. I got the sense that people thought the exam was a little challenging. Sorry about that. It's hard to tell exactly how hard the exams are. But I hope that you guys thought the exam was fair in terms of what it covered and what we asked of you. So don't worry about it being hard. It's not going to matter. That affects everybody equally. OK, so I might finish a little bit early today. I'm not going to go over the multiple choice unless people had specific questions. Everybody will get credit for the first question. I always try to give a fun question on the exam and everybody already fail in some silly way. So I'll just accept any possible response for that.  Any questions about the multiple choice? I didn't think. I will post this solution set after class. I wanted to have it up before, but I ran out of time this morning writing the answer. And it would also be nice to incorporate some feedback from you guys if you have any questions about the exam. So any questions on the multiple? As my old advisor always used to call them the multiple guess questions. All right, I thought these were pretty straightforward. OK, so the short answer questions. Let's start from the most straightforward and work backwards. So the process page tables question. So you were provided with three page tables. Remember, these are 100 byte, no, sorry, 10 byte pages. And the happy OS operates. This means it's two level page tables. It's 1,000 byte virtual address space. Divides the virtual address into a virtual page number and two indices into each table. This is the one time of year where I'm going to be happy that I have a laser pointer. So the first question was, given a virtual address 273, identify the virtual page number offset and first and second level page table indices. Who thinks they can do this one? What is the virtual page number? Anybody want to guess? 27. What's the offset? 3. What are the first and second level page table indices? 2 and 7. Is this a valid address for this process? We didn't ask you to translate it, but it's not, right? Doesn't have an entry in the first top level page table. OK, so the rest of this question asks you to do four more of these translations. I don't know if I can get them all on the screen at the same time, but I'll try. So the first one is 165. So I'll just walk through one of these. So how do you tell how many bytes are for the offset? What was the critical piece of information you needed to have absorbed? The page size, the 10-byte pages. Everything below 10 is the offset. It's the 10-byte page size that allows you to divide the virtual address into a page number and the offset. The page number always identifies units of the page size. Does that make sense? OK. OK, so the first one I asked you guys to translate was 165. The way you would do this is you really didn't need to strip off the virtual page number. You could just go digit by digit. So one, now what this table tells you is the location of the second level table. And there were three second level tables. Potentially, the only wrinkle in this question was that the three tables are not in order. This one points here, five points over here, and nine points there. So I'm such a bad, tricky person for not putting them in order for me. I almost put them in order because that's the natural way to lay them out. But I thought, just see if people are paying attention. OK. So I start with index one. I follow that. I get the second level table located here. Now I'm using the second index, which was six. And that identifies a physical page number of 14. I started with 165. Therefore, my physical address is what? 145. I have mapped my virtual page number to a physical page number. And I just slap on the offset. Any questions about this? OK. The rest of them work the same way. Second one, there was no entry in the second level table. There was an entry for five in the first level table. But the second level table, which again is over here, has no entry for seven. That's the entry for six. The third one, potentially a little wrinkle here too, what index in the first level page table do I need to use? Zero. So I use zero. Zero doesn't map anywhere. So this is also an invalid address. And the final virtual address was 900. That one worked. It took you to the second level table at 320. That one mapped zero to 31. And I got 310. I used the index offset zero from the original virtual address. So this question was, hopefully if you didn't get confused by where the second level tables were, this question was pretty straightforward. Any questions about this before we go on? OK. Let's see. That one's a little bit interesting. OK. So let's do the second question that was somewhat mechanical.  And I've updated the exam to reflect the clarification that was provided during the exam, which is that we ask in about 4K pages. So given this piece of code, the question was, what's the minimum number of pages, minimum, that this process would have to allocate, would have to have allocated at this point in the code? So at some point in the exam, somebody asked about struct-fewer, but it doesn't matter. Because struct-fewer hasn't been pushed onto the stack yet at this point. So the size of struct-fewer doesn't matter. You didn't need to know that because I've hit this point. I have not allocated struct-fewer yet. So the size of struct-fewer is irrelevant. So how many pages? OK, well, two code pages. Why two? Why at least two? Here's one 4K code page right here. This is a static variable. It's going to be in the code section somewhere. We only ask you about code, heap, and stack. It's not in the stack. It's not in the heap. So it's in the code section. There's one 4K page. This is a Uint32. There's 1,024 of them. That's 4K. One page. The other page is for other. At least one more page because there's actually code in this program. There's the code that's located inside the main loop. So there has to be at least one more page. For the stack, I have a similar situation. I've pushed, at this point in the code, I've pushed the bar array onto my stack. So I have a 4K array on the stack. It's very similar to foo. It's a 1024 array of Uint32s. There's a 4K on my stack. And then another stack page for something else. Because there has to be at least one more thing on my stack at this point. And that one more thing could be buffer. I need that's a locally allocated variable. It could be the constant care star that main gets. Remember, when you've done exec v, that gets allocated on your stack. So there has to be at least one more stack page. So at least two stack pages. And then for the heap, at least three, can I silence the constant stream of whispering from that side of the room? Sorry, it's been distracting. And then three heap pages for the 10K heap allocation. This is 10K bytes. I'm using 4K pages, so I need at least three pages of heap. Just to store that data. Yeah, it doesn't matter. It doesn't affect the answer. Buffer, that's true. I probably should have put an allocation definition for buffer, but whether buffer is in the code or in the stack, it doesn't matter. Because at least the code in the stack have to have at least one more page for something. And where you put buffer doesn't affect the answer. Good question, though. Yeah. What's that? Foo? So there's no data segment that we asked about. It's just code, stack, and heap. I still can't really understand the question. But potentially, if you wrote that in the answer, I think we'll accept it. I mean, the code segment that we were considering would have included a statically allocated variable. So if you wrote down specifically that you did not ask about the data segment, but this would be in the data segment, that's fine. If you didn't mention that, it's wrong. It's a reasonable clarification. This is why we do this. OK, any other questions? All right, let's smooom on. Let's see, which one do I want to do next? Yeah, OK, this one, this is too bad. All right, so we asked you, what would you have to change about the OS if I said that you now have to support 48-bit virtual and physical addresses? We've been talking this semester about 32-bit addresses, which were traditional on systems for a long time. But at some point, someone decided that 4GB of RAM wasn't quite enough, and addresses had to start getting bigger. 64-bit addresses are huge, right? I mean, with 64-bits, I think you can address every atom in the universe. So that's maybe too big, right? And 64-bits cause a lot of things to get quite a bit bigger. 48-bits might be a good compromise. So what we need to change? Clearly, page table entries. So we've done some work to get the page table entries to include necessary information about being able to map pages. But that is under the assumption that I'm using 32-bit pages. So I clearly need to do some work to my page table entries. The address space size itself. So this is a design decision. Having 48-bits of addressing does not mean that I have to support larger address spaces. It could mean that I could simply support more of them. So this is potentially something that we've been intermingling these two concepts during this year, and that's pretty traditional because 32-bit address spaces and 32-bit addresses. But you could have decided to increase the address space to 48-bits, and that would be reasonable to do. It's not required. Larger pages, now that I'm not that I'm increasing the width of my virtual addresses to 48-bits, it may not be appropriate to have only 11-bits of offset or 12-bits of offset anymore. I might want to have more. The move to 48-bit addresses is caused by the larger amounts of memory that are available on today's systems so that's the same thing that might cause you to say, you know what, 4k pages are too small. Let's make them bigger. Different page table structures. So the two-level page table structure that we discussed in class is going to start to struggle when you get to addresses that are this wide because the parts of the page table itself are going to have to get quite big. So for example, if I take 12-bits off of my 48-bit page, that leaves me with 36. And so both of those, if I want to have two-level tables that are balanced, they each have it to support 11, 18 times 4. So those get bigger, and actually they're bigger than a page as well. So those would be multi-page data structures. So you might want to go to a three-level table at this point, or something even deeper, a four-level table. And finally, clearly, if you said anything about the MMU or the TLB, I think we're going to give you a point for that, because duh. I mean, the 32-bit MMU is not going to properly map 48-bit addresses. We asked you for two. So any two of these would work. You don't have to use larger pages. No, no, I mean, you could continue to use 4k pages. I mean, I think it would have been hard to justify saying that we should use smaller pages. I would be interested to see if anybody managed to pull that off. That's a hard sell. But no, you didn't have to change a page size, or the address space size, or anything else. Just had to talk about a couple of things that would change. All right, so I think, oh, and actually I didn't complete the second part of this question, and the solution is a good point. So let's talk about the second part for each one of these, for the page table entry.  That's almost inevitable. So my paging-related data structures are going to get larger. Page table entries are going to potentially get larger. The page tables themselves might get larger. If you're still using two-level tables, those levels get quite a bit bigger. Address-based sizes might not affect the memory or computational overhead in a meaningful way. The MMU change is not clear that I can't, it's not clear that I would be able to support the same number of entries in the TLB if I have to go to a larger matching width. But those are some of the implications of some of these changes. Questions about this question? Yeah, I mean, you could argue that I can still have an MMU that should be able to do this in constant time. Let's put it this way, you guys don't, I haven't taught you or expected you to know enough about how that hardware actually works to be able to make the argument. Without that information, I would say, at least it can be done in constant time. That constant might be a bit bigger for a wider page size, but I don't even know. All right, good. Okay, onward and upward. Okay, so eliminating deadlocks. So we talked about eliminating cycles in class. That's the sort of canonical way to eliminate deadlocks when you have the ability to walk multiple resources. But there are some other ideas. So one of the things, the first thing that we mentioned when you look at the four ways to eliminate deadlock is just don't have productive access to shared resources. But that's sort of, that's hard to get rid of. If I have a shared resource, I probably need to be able to use it in a safe way. So that one sort of off the table. That left you with two others. So the other two requirements for deadlock are that I cannot be preempted once I have a resource. So I essentially, I can't take a lock away from a thread once it's acquired one. And the other requirement is that threads are allowed to make multiple requests. And there's a couple of ways to work around both these. So with the first one, and there are systems that actually do this. So in the first case, it's possible that if your clever, if your kernel is clever, it can actually identify a deadlock by seeing a cycle on the weight queue. So if I have some notion of what threads are waiting for, it's possible that I can see on the weight queue that there's a circular dependency. So, and again, there are tools and systems that can do this at runtime. They can detect when a deadlock occurs. That's the first thing you have to be able to do. Unfortunately, once you get there, it's still not clear what to do. How do I fix this? I can shoot one of the dining philosophers, for example, but that's, so for example, I could kill a thread. We will probably give you credit for that if you put that down. Because let's put it this way, it's better for some of the system to continue to make forward progress. Even if it means tossing one of the threads out of the boat, then for the entire system or a portion of the system to just be stuck forever. This is actually, if you guys use, well hopefully you haven't done this to your virtual box, but if you use Linux systems, you'll find that Linux has what's called the out-of-memory killer. And essentially, this is how the out-of-memory killer solves problems. When the system really runs out of memory, I think we'll talk about this next week, what do you do? And I mean both physical and swap space, right? You're totally exhausted. You can't allocate pages anymore. Well, what Linux does, it has this very complicated procedure for choosing a process that just gets terminated. There's nothing else to do at that point. You have a series of bad decisions that are available to you. And so you try to make the best bad decision possible. So you could say, I'm just gonna terminate one of the threads. That should allow the others to make progress. If you wanna do a better job of this, maybe you could provide a way, when you lock a resource for the thread to register sort of an escape hatch, so that if I need to take this resource away from you, here's some code to execute when that happens. You can imagine passing a function or a pointer or a callable or whatever you want that will get run when you take the lock away from the thread. So it's a signal to the thread that I grabbed a lock. Then I went and did some other work. I tried to grab lock two. That had the potential to cause a deadlock. And so the system actually backed me all the way up again to where I had grabbed lock one and said, you don't even have lock one anymore, right? This is a little bit tricky to do. The simplest solution would have just been to say to kill one of the threads. Why not? So in the second case, allowing multiple requests. So this is actually a little bit of a simpler solution, which is to just make it impossible for a thread to hold two locks simultaneously. I go to acquire the second lock and either it just fails, which is probably not the right thing to do, or I assert that I don't have another lock and the thread dies if it tries to acquire two locks at once. Now putting this in place means that you're gonna have to redesign a lot of code because there's a lot of code that safely acquires multiple locks.  But it's not clear that you always, in fact, I would probably believe that you can write the same code and instead of acquiring two locks, you can do things different, right? So this would force you to change how you design parts of the system. Alternatively, and this is something we mentioned in class, you could provide a function that allows a thread to request two locks at once and does not succeed unless it can return both those locks simultaneously. So these were your options for this question. Your questions about this question, yeah. No, this is really a resource problem, right? This is a correctness problem. Remember, the threads that are deadlock aren't deadlock because they're, they're not necessarily deadlock because they're waiting for a resource, right? They're deadlock because they're trying to use the resource correctly, right? So for example, you might have a table as part of assignment two where you allocate pits. Fixing deadlocks on that table, it doesn't work to just allocate another table, right? Because I need one table, right? I need that for correctness. I need to lock it for that to happen. That's a good question. Any other questions about this one before we go on? Okay. So that was the first one. We've done this. Okay, pipes. Here we go. So this question, the thing we were looking for you to mention was simply that by opening a pipe and passing data between processes, I can, I have more information about when a process can make forward progress. So remember, in general, I had this problem which was when a process was blocked. I didn't know how long it was going to remain blocked and it was unclear in many cases what would cause it to be unblocked. But in the case of a pipe, it's very clear. So here's my example, shell pipeline where foo is piping data to bar. I know that if the buffer is empty, bar cannot make progress. And I know what is going to cause the buffer to become non-empty, which is foo running. So if you identify that relationship between these two processes and the fact that that's information that the OS knows, then you're gonna get some credit for this question. How do you use that information? And what's the resource allocation trade off? So for example, if I want, let's say I want to reduce the number of switches, contact switches between foo and bar on a single core system. The way to do this is pretty simple. Run bar until it's finished. Just run it to completion. That way bar has generated all, sorry, run foo, foo's first. Run foo to completion. That way foo is generated, all the data it's going to generate. And I never have to worry about bar blocking because the buffer is empty. Then I run bar. And bar should be able to run to completion because foo is done, right? Bar, you know, when foo completes, the buffer has all the data it's ever gonna have in it. And when bar is done, the output contains all the data it's ever gonna have. What's the problem with this from a resource allocation perspective? Okay, so that's fair, yeah. I mean, I might get more mileage out of using both foo and bars. But what resource do I know is going to be overutilized if I let foo run to completion before I start bar? Where does the data that's piped between these processes go? It's buffered in memory, actually. But it doesn't really matter. It has to go somewhere, right? It has to be stored somewhere. So the output from foo is gonna be stored before I give it to bar. If I run foo to completion, that means I have to store all of foo's output. By definition is the maximum amount of output that I have to store. We did cover in class that pipes are buffered in memory, but regardless of where I buffer the pipe, I have to store all that data. Then I have to feed it all the way into bar. So whatever resource I'm using to store the pipe contents, running foo to completion first will use up as much of it as possible. The tradeoff is if I run bar more often, the buffer size will be smaller. I let foo write some data into the buffer. I stop foo, I run bar, bar clears the buffer out, and then I start foo again. If I go back and forth doing this, I can keep the buffer to a smaller size so I don't have to use as much memory for the pipe buffer. The tradeoff is that I have to stop and restart bar and foo more often. So this is the tradeoff I had in mind when I wrote this question. If you identified another one, like the one that Isaac pointed out, you'll get credit. Questions about this? Okay, let's look at, oh, we did this one. Ah, okay. So separate exception handling paths for TLB and exceptions and interrupts. So the question was why would I use this? Why would the hardware provide this? And the second was how would the OS take advantage of the differentiation? And to some degree, the second question sort of answers the first, right? So the answer to the first question is just sort of because it allows the operating system to do this. If I bundle all the faults together and push them down one path, then there's no way for the operating system to really optimize that, right? All the faults come in in one particular place. So the answer here, there's a variety of different ways to think about doing this, right? But one thing to observe, and another thing to observe with the first part of the question is that what's the other difference between TLB exceptions and memory-related exceptions and other? What do you think there are more of now that we know how virtual addresses are translated? There's a lot of memory-related exceptions. Processes are generating these all the time. Certainly, processes generate way more memory-related exceptions than they do system calls by a large order of magnitude problem. And even when you start thinking about interrupts that are generated by hardware, still the TLB memory-related exceptions are the most common. And in a lot of cases, I can handle those exceptions pretty quickly. So in certain cases, for example, the TLB might generate exception that I can, you know, I look up the entry, I load it, and I'm done. And so because there's so much commonality to the way I can handle some of the fast path TLB exceptions, it can make sense to put them on a separate code path so that I don't have to save some of the state, for example, that I might have to to handle a general-purpose interrupt. So remember, on the general-purpose interrupt path, I'm saving every register that the thread was using. And then I'm gonna restore all of them later. The other thing can be that memory-related exceptions don't have to and frequently don't cause a context switch, whereas interrupts and system calls can frequently do this. So if I have a context switch, there's a lot more information I have to save every time because I need to create a trap frame for that process so that I can restart it later. If I'm on the memory-related system call path, if I can design a handler that uses fewer registers, then I don't have to save those other registers that the process was using because I don't touch them. So, and that's really one of the answers or part of the answer is that if I can eliminate some of the overhead of storing all the state necessary to restart the process, then I can eliminate all of that work that I'm doing when I come in for our memory-related exception. Any questions about this question? I think if you identify that this is something useful because I might wanna handle these exceptions differently if you said something about the number of times that these exceptions are triggered, I think that you'll get full credit for this question. All right, okay, so let's move on to the long answer question. So we have, I think a good amount of time to talk about these, okay. So the first, let's talk about the first long answer question. So the first long answer question is about the size of the operating system interface. So, kind of canonically, people when they compare Unix and Windows, one of the things that they'll talk about is the fact that the Unix system call interface is fairly thin, provides a small number of calls, and probably even a smaller number of those calls are the most heavily used. Whereas Windows, for a variety of reasons, provides a much bigger interface. One of the reasons that Windows provides a bigger interface is because the interface has changed over multiple iterations. And so, I think the 3,000 calls that we put in here, that's only for one version of Windows. If you go back and you look at Windows and its compatibility modes, it's actually supporting a lot more calls because it's supporting old Windows API calls from previous versions. So there's some backwards compatibility problems here. But regardless, the idea is I can implement, I can take the same set of core features and I can expose them through a thin interface that may cause the process to have to compose multiple calls to do what it wants to do. Or I can expose it through a thick interface that exposes some of these calls sort of in a different way. So let's go through some examples of this.   So what's good about a small interface? Yeah, it takes, it's less code to write. It's easier to implement and support. The bigger the interface gets, the more calls there are, the more there is to do. And the other thing is that when an interface provides multiple ways to do things, it can be difficult for app developers to know what the right way to do something is. I've got three different calls that open a file in sort of subtly different ways. Why would I use one versus the other? So maybe I picked the one that I've been using before, but maybe they've added a new one and a new release that's a little bit different. So having one way to do something that's provided by an interface can be very helpful because it constrains the developer to only use one interface. And it also constrains the maintainer to only support that one way of doing. So in the answer, and you weren't required to talk about this, but when people, when there were complaints about Microsoft's monopoly of Windows in the past, one of the things that generated those complaints was the fact that if you, for example, were implementing a web browser called Mozilla, you were working on your Windows version, you had these maybe six different ways of doing this particular thing. And you'd be scratching your head thinking, what am I supposed to use? Which is the one that's going to be supported in future versions of Windows? What's different about that person than the person who's building the Internet Explorer web browser? What thing can the guy who's building Internet Explorer do than the guy who's building Firefox cannot? Yeah. Yeah, he can call up the guy who maintains that interface and be like, dude, what's going on? Which one should I use? Or alternatively, and there were allegations about this, he could call up them and be like, by the way, you know what would be super helpful? Is if you had a system called that did X, where X is this useful thing that I want to do. K things by next interface, there's this new, did that ever happen? I don't really know. Is it likely, of course? I mean, this is co-designed software, so you try to design it to meet the requirements of other people you work with. In any case. Small interfaces, potentially easier to build, but of course the trade-off with the small interface is that, and the nice thing about a large interface is there's all these things that you can feature is you can add to the large interface. You can start to do certain things in different ways, and you might be able to simplify things that require several different calls in the smaller interface. So I'll scroll down to the answer. So we asked you for some problems with the small interface, and you guys could have talked about other things. The main things we were interested in, you guys observed me, was the fact that frequently I have to use multiple system calls to do things that are potentially could be done with one system call. Fork and exec, the conical example of this, right? I have to, in order to create a new process, it's running a new program, I have to call fork, and then I have to call exec. And we've talked in class about why this is bad, and why it would be nice if I had a single system call that would do these things. Open, so here's some other examples. Open followed by read and write. What if I just want to read an entire file into memory? I've got my buffer already, and I don't, or I want to read as much data into the buffer as possible from a file, whatever. Why do I need to open the file, mess with the file handle, I've got to allocate space in the process, file table and do all this stuff. And what if I'm going to just open, read the entire file and close it again? Why can't I do that in a single call? Seems reasonable. Read and write and L-seq, right? So file positioning is implicit in Unix which is kind of annoying, right? If I want to move to the end of the file I have to call L-seq. If I have multiple threads that are using the same file handle, now I have a synchronization problem at the process level which is really gross. So these were examples. And you guys could probably have come up with more. Where I have to use two calls, or more than two calls potentially, to do the work of one call. And of course, there's overhead involved here. Every time I perform a system call, I'm gonna block, some other process may run, but I have to get into the kernel, there's overhead associated with that. So these were the type of examples we were looking for you guys to pull up. And in most of these cases, unless you guys came up with some weird things, there wasn't really a huge amount you had to do to implement this. Other than add a system call number, add a call handler, and implement. So there's not necessarily a lot of new capabilities you would have to handle failures properly. Questions about this problem? Yeah, no, I mean it's not just context, but it's just more kernel transitions in and out of the kernel. Every time I perform a system call, I have to cross the kernel user boundary, there's high overhead to that. So if you think about it, the thicker interface gives me the ability to do more powerful things with the interface, with one call. So again, rather than having to compose multiple system calls together to accomplish what I want, I can use a single call that does exactly what I need. Any more questions about this question? Yeah.  It's not clear to me that thick interfaces don't just compose existing code, but yeah, that's a reasonable trade-out to identify. The thick interface, your kernel gets a bit bigger. The volume of code you have to maintain gets larger, and that's, I think, the bigger problem. It's just harder to maintain. Even if there's a lot of overlapping functionality, I still call it different ways, and I have bunches of different ways that I can get onto the same codebath, and that could be very difficult for people to think about. All right. Okay, so last question was on concurrent system calls. So the idea here is, and toward the end of the semester, we'll look at some fantastic papers about this problem, but now we have these 48, 64, 128 core machines, and shared memory multi-processors capable of running existing operating systems what people have found is that a lot of times, you don't get the scalability that you want on this machine. I double the number of cores. I want my app to go twice as fast, but it doesn't, it only goes 20% faster. And what's been happening, even for programs that are themselves well-written, is that increasing core accounts have started to expose scalability problems within the operating system itself. And in some cases, so again, towards the end of the semester, we'll look at two examples of this. So in some cases, just clever engineering inside the operating system helps, right? So better, so for example, a lock. When I have two cores, this one particular lock was not a problem. I have 48 cores and this lock is a huge problem. And so now I have to think about how to redesign my internal data structures in order to get rid of that lock. But in other cases, it's the specification of the interface itself that's the problem. There are things that the calls are doing, guarantees that they're making that don't scale well. And so this code has to look at example of that. So we gave you this little loop where threads repeatedly open and close a file. And you can imagine that I'm trying to run as many threads as I have cores on the machine.  So I want them all to be able to run at the same speed. They should be able to run on separate cores, essentially entirely independent of each other. The problem here is that they don't. And we told you that this code would not scale well on existing systems. Notice that this code really doesn't do any work. It calls open and close. Just over and over again. Don't worry about why. It's just what it does. And I recognize that this question was a little bit subtle and so I tried to sort of help lead you guys to the correct answer and so the note here points out that open is implemented to return the lowest available file descriptor. That's part of the interface specification. That's something that Linux says that open will do. Now, apps don't rely on this behavior. We're a very, very, very small number. The file descriptor you get back is an int but technically it's just a handle that allows you to identify the file to the operating system. So who cares? Whether it's the lowest or the highest or anything in between, it doesn't matter. So scroll down to the answer again. And I will be honest. I felt better about this question after Monday's review because we talked about a very similar situation in Monday's review, although not identical. So the problem that you encounter when you run this code is that open because it has to return the lowest available file descriptor it has to perform a linear scan of the file descriptor table starting from zero in order to find the lowest file descriptor and technically to guarantee that open returns the lowest file descriptor, I have to lock on top of this loop. I can't even safely do the read lock read optimization we talked about because remember that only ensured that I found a slot in the table. I'm supposed to ensure that if I make three calls to open that the first one gets zero, the second one gets one and the third one gets two. They have to return in that order, that's in the interface specification. And so if we use the locking trick that we talked about on Monday, I could safely ensure that they all got a file descriptor but I couldn't ensure that they were handed out in the correct order. And so now what's happening is every call to open is doing a linear scan to this table with a lock. So I have a critical section. The file descriptor table is a shared resource inside the process so every thread is gonna be looking through this and I'm locking across a long critical section potentially.  How do you get rid of this? Once you've seen it, it's not hard. Just don't return the lowest file descriptor anymore. Change the interface specification to not guarantee that.  And once you do that then the sky's the limit. You can, you know, the ideal solution here would be to just start at a random point in the file descriptor table and start looking for a file descriptor there. Just making that change even if you grab the lock before you look at the table is going to help because remember when I'm returning the lowest available file descriptor the file descriptor is gonna be used in use are all packed at one end of the table. So I'm starting my search at the point in the table where I'm the least likely to find an available file descriptor. If I start my search at a random location I can do better. And once I do this, if you were there for Monday's class you could also have pointed out that I could do the read lock, read optimization meaning that I don't even have to hold the lock outside of the loop. I could just grab it inside of the loop. So again, this is an example of how a very small change to the interface specification allows me to remove a scalability problem. Questions about this problem? I have a sneaky, how many people did this problem? Oh okay, maybe some of you. I was worried this would be like the problem that no one answered. Okay, any overall questions on the exam? So your last chance to influence our grading has already successfully earned one point back by defeating my fun question. Any other questions on the exam? Okay, on Monday we will go back to talking about virtual memory and we will talk about page replacement policies and how to ensure that we swap out and in the right pages. I will see you then.


Hi. First up, I want to say thanks so much for clicking on this video. It means the world to me. Secondly, I want to say welcome to the first of my eShop list videos of 2019. This video was number, I don't know how many, in a series of videos where if you're new around here, I plucked 10 games from the eShop that I feel are really fun, engaging, exciting, addicting, a bunch of other adjectives. Are those adjectives? I should have finished high school. Just great games on the eShop that maybe you should check out if you like the look of them. The premise is pretty simple. I don't know why I'm bothering to explain it. And I picked some really awesome games today and I have a lot to say about all of them.  If you're new around here, please smash that subscribe button and make sure you hear full lip all over the like button while you're heading down that way anyway. Without further ado, let's talk about some really fun eShop games. And the first eShop batter stepping up to today's plate, that is my video is Downwell. Am I pointing to it? Yeah, there it is, Downwell. By far one of the cheapest games on any of my eShop lists at just $2.99, Downwell is absolutely worth the price and then some. The entire game has you falling down, well, a well, but you're falling in style. Just like Bayonetta, you have gun boots that pack a huge punch, that keep you afloat for just a moment while you blast away at any enemies or bricks directly below you. Downwell is so simple in design, from its 8-bit visuals to its gameplay mechanics, but its fast-paced action is hard to put down, making it a perfect pick up and play a few rounds kind of game, which is exactly what you want on the Switch. The further down into the dark well you fall, the harder and weirder the enemies become. In classic Mario style, you're able to head stomp on most of the enemies, which gives you a satisfying amount of recovery time which you can use to plan your next move, but not all enemies will appreciate being stepped on, and with only four hit points you don't want to make any mistakes.  You will find more than you expect in the well, from shops where you can buy helpful items or hidden caves filled with relics, and after every stage you get to pick a power-up or if you're lucky get some health back since your hit points carry over level to level. This game is not easy, it took me more attempts than I would like to admit just to get to the fourth level, so bring your A-game or prepare to go down. Get it? Like down? Like down well? Like cause you're falling down the- the wha- the well? The game's just a few dollars, try it out. While Goat Simulator might be the most expensive game on this list, sitting at $29.99, which really 30 bucks ain't that bad, it's also the game that'll bring you the most unadulterated joy. The concept is incredible, so you have all these simulator games, right? Like farming simulator, bus simulator, you know the types, they're supposed to make you feel like you're working a 9-5 on a tractor all day, super serious stuff. Well someone had the amazing idea, what if you were a goat and just like nothing made sense?   I can't stress that enough. It's been compared to the old school Tony Hawk games, except instead of being a skater, you're a goat and instead of doing tricks, you're wreck stuff. Licking objects attaches your tongue to it and you can literally drag anything around for as long as you want until you're done with it. And at any time you can just drop your goat into rag doll mode, which is when you start to experience the most fun you can have with the game's insane physics engine.  In fact the developers have promised not to fix any of the bugs and glitches because they find them so hilarious.  So you might find yourself bugging out into the floors or walls amongst all the chaos you're creating. The game is literally a parody on the Simulator franchise and it's one big meme in on itself. This Switch Pack also includes Goat MMO Simulator, Goat Zzz or Goat Z, which is a zombie DayZ parody, Goat Simulator Payday and Goat Simulator Waste of Space. These are all fleshed out DLC versions of the base game. For example, the Goat Z Pack even features crafting and of course zombies. So go get your goat on and start tweeting me all the bugs and glitches you find. Next we have Momodora and hey look at us, we almost made it through an entire eShop video without a Metroidvania game in it. And don't take me the wrong way, I love a good MVG as much as the next human being, it's just these lists are usually filled with them. Today though, we just have the one, which gives it a perfect chance to stand out from the crowd. Momodora easily boasts one of the most impressive pixel art styles I have seen.  And the art direction is very Castlevania inspired. The game also has extremely tight controls and combat similar to Dead Cells, but it feels heavier like my attacks have more weight behind them and I do like that. This game offers everything you would expect from your classic Metroidvanias, hidden items and power ups, loads of enemy types, cool boss battles, great music, a small amount of backtracking and you know, the usual stuff. Oh and you know what, this is actually the fourth game in the series. Yeah, I know, how do we miss all of these? Thankfully, this one is a prequel so it's a fantastic place to start your Momodora journey.  It's absolutely worth its price, however, it's not the best Metroidvania style game I have played. Momodora didn't really do anything to transcend the genre or do anything really that different at all.  Again, it's absolutely worth the price, especially if you're looking for your next hit of Metroidvania goodness, just bear in mind those other things I said. Wait, wait, wait, wait a second, didn't Enter the Breach launch in August of last year? Did I really forget to talk about it? Yes, I know I did because every video I have made since then, y'all have let me know that I have not talked about Enter the Breach yet, sheesh! Although if I'm being honest for a second, I really appreciate the fact that when I miss a game that you really want me to talk about, you let me know it repeatedly. That actually means the world to me, so let's talk about Enter the Breach. I played it just for you guys. Enter the Breach is a turn-based strategy game set in the far future where humanity fights against giant armies of monsters.   And this is where things get real, because Enter the Breach can be brutal. You'll have to constantly adapt to ever-changing scenarios, forever having to find new ways to survive the horror. As frustrating as it is to have new enemies spawn out of nowhere without you expecting it, it forces you to stay on your toes and expect the unexpected. Even slight screw-ups or miss-clicks can cost you the game, even if you're doing well. In some of the later missions, most of your gameplay time will probably be you just wondering what the heck is the best move to make next. The game isn't about flashy colorful visuals or an in-depth fleshed-out story, it's all about the tactics. For hardcore fans of the genre, this game is a must because it's one of the best in the genre. Did someone say flashy colorful visuals? Oh yeah, that was me. That was me that said that. Ah, I remember. Another game I missed out on until now is Guacamelee 2. It's a Metroidvania- Damn it. It's so close. I guess there's two on this list. Honestly though, this game doesn't really feel like an MVG to me. It's more like a brilliant platformer with excellent level design. And it's just bursting with color. I mean, literally, I'm pretty sure they used every color there is in existence. Blues, reds, indigos, violets, the rest of the colors. I'm not naming them all. This game is filled to the brim with sharp wit, buttloads of charm, and just the right quirky sense of humor that I love. I mean, just look at this goat telling me that I'm in the darkest timeline. That right there, that's a game I want to buy twice. Guys, I've been thinking about that night over and over. One thing has become clear. This is the darkest, most terrible timeline. You play as Juan, having to travel timelines to save the world, and it's all up to him because out of all the other timelines, he is the last one alive. I love this game. As you platform, you switch between the worlds of the living and the dead, and in doing so, certain objects or platforms will appear and disappear. This leads to gameplay that has you having to think fast and adapt to your situations quickly, or else you might find yourself dying in the wrong world. You also have the ability to turn into a chicken. You can not only use this to access areas you couldn't before, but you can also use different attacks and abilities in the chicken form. The combat is really fleshed out with loads of different fighting abilities, as well as an upgrade system. And if you think just the single player is fun, it's an entirely different feeling game when you play with friends, especially for players.  Is it still okay when I make the joke? I hope so. For the first time ever in my eShop videos, I'm kind of cheating on these next two. I mean, I guess I'm cheating. They're my videos, right? Can I cheat on my own videos? It should be my roles. Am I breaking my own roles? What a conundrum. Let me explain what I mean. Okay, so I usually don't add games to these lists that I've already talked about before and reviewed somewhere else. But recently I worked on a really great video with my friend Bob Wolfe over from his channel WolfeDen, and I decided to check out both Donut County and Gunman Kly for that video, and I fell in love with both of those games. And I felt like in a weird way, I owed it to those games to have them featured in an eShop video, because as far as me reviewing eShop games go, I kind of see this series that I make on the eShop lists kind of canon to the eShop games I've reviewed. Does that make sense? And I don't want these two games to be excluded from my eShop games worth buying. If you want to see me review those games, you will have to go and watch that other video, which you can see by clicking up here somewhere, or clicking at the end of the video, or checking the description down below. There's so many ways. You can just Google it. I don't care. If you're just really fricking lazy, and you don't want to do me a solid by watching that video, though, let me summarize these two games.  And Gunman Clive is the best Mega Man style game that you never played. They are both super cheap and super fun, and if you need more convincing, what's that other video? Oh holy moly, I am so glad Firewatch came to Switch. This is a game I fell in love with on my Xbox quite a while ago, and now you guys can finally check it out for the first time if you also didn't play it elsewhere before now. And I just, I recommend this one so much. It might be my second favorite on the list. Firewatch is a game that you will either absolutely love, or you just might not. It depends on how you feel about walking Sim kind of games, like the Telltale's Walking Dead. In other words, if you love fantastically written story-driven games with beyond brilliant voice acting and a roller coaster of emotions, just waiting for you to jump on and start going through the loop-to-loops, then you're in for a treat. But if you're the kind of person that looks at a game like The Walking Dead and thinks, I don't like quick time events and there's not enough shooty-shooty bang-bang, this might not be your game. Firewatch puts you in control of a man named Henry, who has found himself in a very difficult place in life. So he decides to escape to a job in the middle of no west, specifically the middle of a national forest in Wyoming in the year 1989. I mean, this place is so far in the middle of nowhere, it takes him two full days just to hike to his tower where he will be living and working throughout the rest of his career as a Fire Lookout. And it's the story that's told from this point on, that's just something truly special. Henry is almost all alone out here, other than a new friend he found in a fellow Fire Lookout named Delilah. And while throughout the game, you can see Delilah's Lookout Tower in the distance, your only contact with her is through walkie-talkies. And through these walkie-talkies, is where the entirety of the story will unfold. And again, it's just brilliant. You talk to Delilah every day about work, life, your past, everything and anything you choose to open up about, learning more about your own character as well as Delilah's. The dialogue here is captivating, at times it's laugh out loud funny, where some of the wisecracks or jokes tell each other, And at other times, it'll get so intense, that you won't even want to pick a dialogue option. The voice acting is so real and believable at times, that I honestly forgot I was playing a game, and truly felt as though I was listening to real people talk and learning all about them. What happens to these characters as the story unfolds, made me legitimately feel uneasy. As a mystery arises and you start to feel as though you are constantly being watched or followed by something or someone. I wish I could talk about this game more and dive into more of the details, but I fear anything other than what I've said would be a spoiler. So I guess I'll end it by saying the art direction here is incredible, and since you're watching a video review, I really don't think I have to elaborate on that any more than I just did. The next one is One Strike. When I started looking at the eShop's latest releases to make this video, I saw One Strike had released fairly recently. I grabbed it and enjoyed it, and obviously decided to put it on this list. It wasn't until later that I found out that this wasn't actually the game's first time on the eShop. Apparently it was released back in October, but then it was swiftly pulled down for copyright infringement. The director of the game paid a composer for the music, and both the developer and the publisher were convinced that the track was an original work.  The track was stolen from a Famicom game. And now the game is back on the eShop with all new music, and I say we showed the game some support because it had quite a big setback through all of this. And it's a fun game. Let me talk about it. For only $4.99, this is another cheapy cheap game worth the price. You have three difficulty settings, easy, medium, or hardcore, and trust me, you'll want to start on easy. As the name of the game would suggest, you get hit once, you die.  So simple, and yet so freaking intense. There is seven different fighters to choose from, each of which have different weapons and fighting styles. Believe it or not, with just having a forward dash, back dash, block, and one attack button, it still takes quite a while to master each fighting style of the characters. In one life mode, just like in real life, you only got one. Life, that is, if that wasn't clear. You can't get hit by any of these fighters even once, and I'm telling you that's not easy, even on easy. There is also arcade and team duel mode, as well as tournament mode for up to eight players, AI, or real people. Again, it's another game that's just so simple in design and gameplay, and yet as soon as you block that first attack and then land the killing strike, you'll watch your opponent fall and then realize, I just want to keep on killing. That was darker than I intended. Yeah, so I'm pretty sure like 95% of you have been waiting for this one ever since you clicked on the video. Let's talk about it. In just the first few days since Wargroove released, it's already solidified itself as one of the best digital games on the eShop. In fact, I'll be surprised if this doesn't make it into my top 10 eShop games of the year round up later on this year, and if that's the case, hello future me and future you guys. How was the year? Did anything terrible happen? Am I dead? I could be dead. I mean, I really could be. Let's not think about that. My own mortality aside, needless to say I love it, and it's an absolute steal at 1999. Wargroove is a turn-based tactics game, and for those of you that have been paying attention throughout this video, it's my personal favorite turn-based tactics game on this list. I apologize to the other one on this list. I just love this game. It's inspired by those old school advanced war games that we all loved on the Gameboy Advance, and they did a fantastic job at bringing their vision to life with its high resolution pixel art and fantastic gameplay mechanics. Just like those advanced war games and similar to Fire Emblem, you control multiple units on the battlefield. Each turn, you're able to move your characters once each, as well as attack or use their abilities once each per turn. There are loads of different unit types. Archers who can attack from a distance and can deal even more damage if they don't have to move that turn. Swordsmen, they need to attack up close, but deal more damage if their commander is nearby. Even adorable pop-pups who do more damage in packs and can be used for scouting ahead. But we don't talk about what happens when they don't... Come on, man. They're not real. They're just digital fake pop-pups. They're not real. Anyway, there's a ton of other unit types on top of that. I've made my face red now by touching it a bunch. I wasn't really crying. Kind of was. As well as your commander in each level who also have their own special groove ability, which is a different ability for all 13 of the playable commanders. Some will be able to heal troops or raise their defenses and stuff like that. Positioning and thinking three moves ahead of your enemy is everything in this game. However, there is so much more to think about on top of that, such as the effects certain terrains will have on your units, or which units are weak to others. The game can be a real challenge too, which is a great thing. The last thing you want in any strategy game is to not have to use any strategy. If it was super easy, it would be super boring. And just to add even more bang to your buck, Wargroove has local and online multiplayer, including versus and cooperative play. And there's also campaign editing tools, which allows you to create your own maps. Its overall quality and polish is undeniable from the visuals to the story and the gameplay. There is so much more I could gush over in this game. There's so much more I could talk about, but instead I'm just going to leave it on, have fun.  So by saying have fun, it was like a funny hard way of saying just why talk about it because I know you guys are gonna play. Subscribe please. I'm losing my mind over here. There's another 10 games on the eShop worth buying and honestly I feel like today's list was very solid. I actually think we're on episode number 12 of these now, so what like 120 of these eShop games. I have a lot of fun playing them and reviewing them for you guys and I hope you like the videos. And if you do, make sure you have flip all over that subscribe button. Click or tap on this video right here because this time next year I might be dead.


Yeah, pretty sure we die here. Unless, unless we heal. Heal it. Oh, how's it going everybody? Datsudoi here back with some more Dragon Ball Fighters ranked matches. And this is actually really scary. It looks like we're going up against a insanely high ranked Goku Black Player. Our team is nothing too special. It's just a bunch of characters that I think play a really fun neutral game. Definitely wasn't expecting this kind of opponent. Wait, was he also playing Bardock? I was like, wait, what's happening? I think this is my Bardock. Yeah, it is. Okay, so I'm classic Bardock. He's got the red look going up. All right, already off to a terrible start for me. No tech. Dude, his assists were both down. All we needed to do was land that tech and we could have gone on the offensive. Now we got to deal with the Goku Black Beam Assist. A little bit of lag. I think that, yo, this guy is, this guy's a menace. It was a little bit of lag that actually almost screwed him up, but he immediately went back on the offensive. He was like, nah, I'm ready. Nice. We get a dragon rush right back. Pretty much the same strat actually. I just ran out of other assists. So I was like, really? That was extremely Bardock of him. Love to see it. Okay, I didn't know how many jumps I had left to be completely honest with you. No, we could have, we could have ended it right there. Oh, that was really smart. I thought my tag would be good there. If he stayed in, it probably could have been. Yeah, shoulder. That move is too good. Oh, no blocks for me. That's going to cost me a whole Bardock. Dang, Bardock. Bardock in general in this game got treated very poorly on both sides of the teams. Bardock was just a punching bag for Vegeta. Vegeta, I said one, the one character that is not in any way, shape or form in this game, huh? Damn, that is good. Nice. But yeah, just Piccolo and Goku blue.   Okay. Nice. I'll take that. That's probably going to get yes. That's going to get Bardock as well. Sick. A punching bag for Piccolo is what I was trying to say. No better time to finish that sentence than now. Only on Dr. Noy's channel can you find people finishing their sense. Oh, six minutes after it was relevant. That was really good though. We're back in the game in a big way. Now, of course, he, he did seem like he was more of a Goku black super fan. All the titles really led onto the fact that I think Goku black is probably a monster character form. Air dash with the sparking benefit is going to give us that kill. And now we're down to his character. This is probably his character. That's what I'm afraid of. Super dash was pretty obvious there. I thought we might be able to match it. That's bad. He still hasn't even popped the sparking yet. My timer. No. And our sparking is just now leaving the building probably. It's not even giving us any heels. I'm a tech. Yeah. Yeah, I deserved it. There's a lot of there's a lot of risks that come in with that UI tech. But I had it. Dude, we should have if we woke up and matched that would have been big brain.  Goku black players dive, kick and neutral. I am being completely 100% honest with you. Nice. Really, really good. Hard to put. Hard to say it's not. We're going to take a hard knock down here. That's the only mix he had. I couldn't, I couldn't block it. This is why this is why I say go, this is why I bring up the fact that Goku black is looked here because he doesn't have mixed. But I always, I always mentioned against me. It really don't matter. That was a really good game. He's going to take us out with a level one. We really need to tech those DRs in the clutch. The game started by getting DRd, and it's going to finish by getting DRd. Dragon rush, really powerful tool. Can we make it back though? I don't think we played too bad. All things considered, we did get a really lucky double Sunday though. So it's not like we don't have, we didn't, we had elements going for us and against us that match. So game two is really going to be the decider. Nice. Do I have a nice off?  I'm gonna hit you with this. I want to know why his one, my nice tech. Again, I ran out of assist. Okay. Couldn't do much off of it though. Hard cancel. Oh, he's moving. He's moving, but we got him. We got him. Lock him down. No jump. No bad back dash. He doesn't get anything off it though. My neutral is getting increasingly lazy.  Maybe I should have even gone with Bardock B. I thought that would be a bit too much, but maybe that's exactly what the DR ordered. A little Bardock B for this team. Okay, we're going to get out with that. Not bad. I honestly, I'm okay with that. He's got to switch us out if he wants Piccolo, which is fine. I'm becoming nice and instant. C assist on Bardock. Who is this man? Oh, come on. Let me confirm that. Let me get that confirmed.  Dang. All right. So Piccolo is out of the limelight. Real unfortunate too, because if we got that confirmed, we probably could have kept him around for a bit longer. Oh, let's see. I'm not confirming anymore. Get that C assist out of here. Is that C assist even decent? I don't know. It doesn't seem that good to me. Seems like a worst gojitas. Then again, my vanishes are not decent. So what do I know? Three bars. We're dead for that. Yeah, that's going to kill from that range. Who's he bring out for point? Bardock? Yeah, Bardock is on point. Okay, so it looks like he likes saving Goku Black for anchor, which is interesting to me. I got to make this come back. I should have used Spark a little earlier. The jump was good. Oh, we should have sparked way earlier in the set. Remember, early sparks are better than not getting to use Spark ever. Got to play better. Got to get that. Do I still have a jump? Okay, we did. So this, that could have been worse. Thankfully, you are Goku's busting.  I don't know why I went for the flips this late. Stop. Oh, the run up dragon rush was sick. It was sick. I'll give it up. I think we die here. Yeah, pretty sure we die here unless, unless we heal. Heal it.  What a play. Oh, we blocked it. What a play though. We're still in it. Yeah, I don't think that C-assist is super. I don't know. It seems a little, for what it is, it seems kind of slow. That was sick though. I'll give it up. That was a really good game for him. Can we bounce? It's already 0-2. No, we can't go out like that. We can't go out like that. I'm, I need a game where I can confirm this stuff. We need to be able to confirm off these hit. Oh, right. We should probably do a stand back. That's for last time. That, that has nothing to do with that match. That was for the last stamp. Oh, punching me. Nice. I'll take it. All right. What do you do? That was so bad. Oh, that was so bad.   What am I doing this game? Like literally, what am I doing? Okay. I deserve to get crossed out. I made so many mistakes that I actually, the universe had to repay me with that. Oh, that was good. Wow. He regained so much blue health. I guess we just give up corner here. Yeah. It's a shame we could have had Bardock so many times. Nice Super Dash. I agree was the correct option. I also went for it. I was like, oh, Super Dash is the only option here. All right. I think I'm going to spark here actually, because he might come out with a level three with Goku blue. Bardock, really? Okay. Okay. I needed Bardock to be out. I need to get this. I need to knock him out. It's actually bothering me that he's alive. 6M a little obvious, or not even obvious, but a little too slow for the current game. Oh my goodness. And over me. No, I was not ready for the side switch.  Beemuses don't ruin that. Oh, Beemuses came in huge, huge Beemuses there. Now, and now Bardock's back out. Oh, I'm so depressed that we couldn't get Bardock on the first go around. Look at it. Look at how much blue life he recovered. My man is thriving in this environment. Nice. I had to be a Bardock player though. That wasn't really bad as this call, but it worked out.   Okay. That combo. I thought I reacted too slow.  That was bad. Okay. Hold on. If we get Bardock, I'm feeling good. If we get Bardock, I'm feeling great. Nice. Nice. Whoa. Whoa. Whoa. I did not expect that to hit. Nice. Okay. Okay. Okay. Now it's a Goku blue with spark, which if it wasn't for all of his player icons, I would probably say is scarier than Goku black, but ex kick was smart. He knew I wanted to get that. Love to see it. Get back. Get back. Nice. Two H. I didn't think you'd have time. Three bars. Not going to spend it. We're out. I got clicked. Oh no. Getting out of that corner was the hard part. Getting hit right there just kind of threw that away. Nice. That is huge. That is actually huge for us. It gives us a little bit of damage. A level three on top so we can get our situation better. The question is what does he do though? I don't want to risk anything. It tracks in the air. The move is good. Again, don't level three. Oh no. I played the dive kick. Wow. That rinsed me. I knew obviously it would kill, but I didn't know the punches were going to kill. Okay. We got to go one more. I played so slow. We know the dive kick was such a threat that I never even considered. We got hit by that earlier too. And I was like dive kick was a great tool there. And I did it again. I ran forward. I was like, what's he going to do? Dive kick me. Oh, dive kick. Right in the face. Give me another match. Please. Please. Please. One more match. Just one. All right. Quick. Quick. One more. One more type of deal here. Love to see it. I'm so confident that 2H as well. Damn. The smile on my face really turned to a frown real quick. I was like exactly as planned. All right. I'm sparking. What is this nonsense? Blocking in 2020? I don't think so, bro. You must not have heard the memo. We don't block. Nice. No, I don't know why I thought that made me intangible. I was like going to go behind, get the cross up, feeling good. Fighting. Listen for the announcer. Love to see that. Get down there.  Oh, no. I trusted you, Bart. The one time I trusted you fully that you would be able to do that. Oh, that was a nice heavy. Damn. I definitely pushed it. Again, I just couldn't finish the job on a low health character. That's been a bigger deal to set than, yeah, air tracking to a CSS. That's where it's good in the air. But then again, you could just time most CSS. That's why Gojitas wasn't really as good as I... Gojitas CSS I thought was really good because it tracked in the air, but at the end of the day, it's not too hard to just time them on the ground if that's where you got to make it happen. Look at that. Like, what is that? How did you win that? Nice. All right. Well, this kind of just earns us our spot in the corner back. But we do have Piccolaeusist. You got hit by that? Why are you getting hit by that stuff? That's fine. We got Piccolaeusist. Yeah, I expected spark there. I didn't know what I could do to stop it, but nice. Good reflect. Do it too. Don't tell him I didn't. Don't tell him I didn't, guys. Give me that. Finally, we got a character. Okay, come on. Come on. Focus up. Focus up. We're not playing our cleanest in this game. He's going to go EX. There we go. It's fine. One, two, a little bit of another one, and one more just for old time's sake. Love the explosion effect. Here we go. Now we got the character lead. It's not... I don't think it's the first time we've had it. I might be speaking. I don't know. Let's go, dude. I knew... Dude, this was such a frustrating set. I'm glad we finally were able to put it all in the practice though. There we go, dude. I'm still... You can tell I'm still not sure if it's over. There we go, dude. I'm glad we were able to put that all in the practice. Of course we can't. We have to... He's free to leave, obviously. He won the set. He three-odd me, but as... You can't just win and leave. So I got to extend the rematch if he wants fully... Legally, he can leave. Legally, he can leave if he would like. I would never blame him. I would never blame him. I don't know why I'm on ice. Do whatever you like, man. There's no need to feel attached. I totally get it. If you want... If you'd like, we could stay. Have another match. I have another cup of coffee. Hey, if you wanted to leave, you could have just left. You could have just left. You didn't have to hit me with the 30-second... Okay. You could have just left. Okay. Well, hopefully you enjoyed that set. I really did. That was a great game. Thank you to my opponent. Unfortunate that that's how it went down. But I'm glad we were able to somewhat get it together there at the end. If you enjoyed it, make sure you go down below, leave me your favorite moment, and hit the like button and subscribe to the channel if you enjoy videos like this one. And hey, there should be some other ones on your screen right now. Other than that, I have been Dr. Doi. Thank you so much for watching this video all the way to the end, and I will catch you in the next one.