Skip to content
Snippets Groups Projects
Commit 1c187159 authored by Matt Bovel's avatar Matt Bovel
Browse files

Move instructions

parent e4ee3453
No related branches found
No related tags found
No related merge requests found
- Empty handouts and instructions: <https://gitlab.epfl.ch/lamp/cs206/-/tree/master/previous-exams/2021-final>.
- Solutions: <https://gitlab.epfl.ch/lamp/cs206/-/tree/master/previous-exams/2021-final-solutions>
# Problem 1: Futures
## Setup
Use the following commands to make a fresh clone of your repository:
```
git clone -b concpar21final01 git@gitlab.epfl.ch:lamp/student-repositories-s22/cs206-GASPAR.git concpar21final01
```
If you have issues with the IDE, try [reimporting the
build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#troubleshooting),
if you still have problems, use `compile` in sbt instead.
## Useful links
* [The API documentation of the Scala standard library](https://www.scala-lang.org/files/archive/api/2.13.4)
* [The API documentation of the Java standard library](https://docs.oracle.com/en/java/javase/15/docs/api/index.html)
* [The API documentation of the Play framework](https://www.playframework.com/documentation/2.8.x/api/scala/index.html)
## Exercise
In this exercise, your task is to implement a leaderboard webpage for a GitLab-based exam. This leaderboard is live in the sense that it is constructed on the fly by extracting grades directly from GitLab pipelines: it is always up to date.
This exercise uses the Play framework, a popular web application framework.
Play is entirely asynchronous and uses futures for concurrency.
In `src/main/scala/concpar21final01/MyComponents.scala`, we define a minimal Play application to display the leaderboard (you do not need to modify this file).
You can start this application using `sbt run` and open the leaderboard in a web browser at [http://localhost:9000/](http://localhost:9000/). After having completed this exercise, you should see a populated leaderboard as shown in this screenshot:
![leaderboard.png](leaderboard.png "leaderboard.png")
In this exercise, your task is to implement the `leaderboard()` method in `src/main/scala/concpar21final01/Problem1.scala` which asynchronously retrieves and sorts student grades.
Grades should be sorted such that maximum grades appear at the head of the list, like in the screenshot above.
Your implementation should use the following two methods to get the list of students and the grade of a particular student:
```scala
/** Retrieve a student's grade using GitLab's API */
def getGrade(sciper: Int): Future[Option[Grade]]
/** Retrieve the list of enrolled students from IS-academia */
def getScipers(): Future[List[Int]]
```
These methods have mock implementations that return made-up values after a short delay (simulating a network call).
Your implementation should be asynchronous (it is forbidden to use the `Await.result` method).
Furthermore, given the large number of students, calls to the GitLab API should be made in parallel such that the overall request is completed in about 1 second.
*Hint:* this exercise can be solved without writing any recursive functions! You are allowed to use every function defined on [Future][1] and [List][2], as well as functions defined on their companion objects ([Future][3], [List][4]).
[1]: https://www.scala-lang.org/api/2.13.4/scala/concurrent/Future.html
[2]: https://www.scala-lang.org/api/2.13.4/scala/collection/immutable/List.html
[3]: https://www.scala-lang.org/api/2.13.4/scala/concurrent/Future$.html
[4]: https://www.scala-lang.org/api/2.13.4/scala/collection/immutable/List$.html
final/dry-run/concpar21final01/leaderboard.png

53.5 KiB

# Problem 2: Actors
## Setup
Use the following commands to make a fresh clone of your repository:
```
git clone -b concpar21final02 git@gitlab.epfl.ch:lamp/student-repositories-s22/cs206-GASPAR.git concpar21final02
```
If you have issues with the IDE, try [reimporting the
build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#troubleshooting),
if you still have problems, use `compile` in sbt instead.
## Useful links
* [A guide to the Scala parallel collections](https://docs.scala-lang.org/overviews/parallel-collections/overview.html)
* [The API documentation of the Scala parallel collections](https://www.javadoc.io/doc/org.scala-lang.modules/scala-parallel-collections_2.13/latest/scala/collection/index.html)
* [The API documentation of the Scala standard library](https://www.scala-lang.org/files/archive/api/2.13.4)
* [The API documentation of the Java standard library](https://docs.oracle.com/en/java/javase/15/docs/api/index.html)
## Exercise
Your task is to implement a couple of actors in Discord like service. You will only focus on two actors in the system: (1) the `NotificationService` and (2) and a `DiscordChannel`.
A user will be able to post messages on the channel and retrieve the messages from the channel.
A user also has the ability to (un)register from the notifications.
Before it can receive requests, each channel is initialized with a notification service.
When a message is posted to the channel, it tells its notification service to notify the users.
The following diagram shows how the communication between actors works. Requests are within `()` and responses are within `[]`.
```none
(Post) [Active]
(GetLastPosts) ┏━━━━━━━━━━━━━━━━━━┓ [AlreadyActive] ┏━━━━━━━━━┓
┌──────────────────────>┃ ┃──────────────────►┃ ┃
│ ┃ DiscordChannel ┃ ┃ System ┃
│ ┃ ┃◄──────────────────┨ ┃
│ [Posts] ┗━┯━━━━━━━━━━━━━━━┯┛ (Init) ┗━━━━━━━━━┛
┏━━┷━━━┓ [NotActive] │ │
┃ ┃◄────────────────────┘ │
┃ User ┃ │
┃ ┃◄────────────────┐ │
┗━━┯━━━┛ [Registered] │ │ (NotifyAll)
│ [Notification] │ │
│ │ │
│ ┏━━━━┷━━━━━━━━━━━━━━━━┓ │
└───────────────►┃ ┃ │
(Register) ┃ NotificationService ┃◄─┘
(UnRegister) ┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━┛
```
Your tasks in the exercise will be to:
TASK 1: Complete the implementation of the `NotificationService` by implementing method `receive` to handle messages from `NotificationService.Protocol`:
```scala
def receive: Receive = ???
```
TASK 2: Complete the implementation of the `DiscordChannel` by implementing methods `nonActive` and `active` to handle messages from `DiscordChannel.Protocol`:
```scala
def nonActive: Receive = ???
def active(notificationService: ActorRef): Receive = ???
```
### NotificationService protocol
* __Register__: Registers the user for notifications and responds with a `Registered(true)`.
* __UnRegister__: Un-registers the user for notifications and responds with a `Registered(false)`.
* __NotifyAll__: Sends a `Notification` to all registered users.
### DiscordService protocol
The channel can be in one of two states: _non-active_ or _active_.
* __Init__: When _non-active_, responds with `Active` and the state becomes _active_. Otherwise responds `AlreadyActive`.
* __Post__: When _active_, stores the message and sends `NotifyAll` to its `NotificationService`. Otherwise responds `NotActive`.
* __GetLastPosts__: When _active_, responds with `Posts` containing the latest messages (ordered from most to least recent). Otherwise responds `NotActive`.
### Running the code
Apart from the grading tests, we include a `@main def debug` method, where you can write your own tests to help you debug your implementation. This method is __not__ graded. It can be executed using `run` within SBT.
# Problem 3: Concurrency
## Setup
Use the following commands to make a fresh clone of your repository:
```
git clone -b concpar21final03 git@gitlab.epfl.ch:lamp/student-repositories-s22/cs206-GASPAR.git concpar21final03
```
If you have issues with the IDE, try [reimporting the
build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#troubleshooting),
if you still have problems, use `compile` in sbt instead.
## Useful links
* [The API documentation of the Scala standard library](https://www.scala-lang.org/files/archive/api/2.13.4)
* [The API documentation of the Java standard library](https://docs.oracle.com/en/java/javase/15/docs/api/index.html)
## Problem description
### Preliminary: File handling
In this exercise, we will work with files using the following trait to handle
all file operations:
```scala
type FileName = String
trait FileSystem:
/** Create a new file named `file` with the passed `content`. */
def createFile(file: File, content: String): Unit
/** If `file` exists, return its content, otherwise crash. */
def readFile(file: File): String
/** If `file` exists, delete it, otherwise crash. */
def deleteFile(file: File): Unit
```
Note that to make testing easier, the actual implementation of `FileSystem` we will use
won't actually create files on disks, instead it will simply use a `Map` to
represent files and their content in memory, but that's an implementation detail
that won't affect how this exercise should be solved.
### The update distribution problem
You work at a game company on the popular online game EPFNite™. Your job is
to distribute game updates to the players from the update server represented by
the following class:
```scala
class UpdateServer:
def fetchUpdate(): Option[String]
def newUpdate(newName: FileName, newContent: String): Unit
```
The requirements of the update server are as follows:
- When a player starts his game, it connects to the update server which starts a
new thread and run `fetchUpdate()` which should return the content of the latest game update
if one is available.
- When a new version of the game is available, the developers call
`newUpdate` with the name of the update file and its content.
- Storage space is limited on the server, so when a new update is stored on the
server, old ones must be deleted.
Based on these requirements, you come up with the following implementation:
```scala
class UpdateServer(fs: FileSystem):
@volatile private var updateFile: Option[FileName] = None
def fetchUpdate(): Option[String] =
updateFile.map(fs.readFile)
def newUpdate(newName: FileName, newContent: String): Unit =
val oldFile = updateFile
fs.createFile(newName, newContent)
updateFile = Some(newName)
oldFile.foreach(fs.deleteFile)
```
Unfortunately, it turns out that reading a file is not an atomic operation: if
you delete a file while another thread is reading it, your program crashes.
Theoretically, you could solve this using locks to make sure `deleteFile`
is never called at the same time as `readFile`, but this solution isn't good
enough for EPFNite: it's important that a player is never blocked from playing
the game because `fetchUpdate` is waiting for a lock to become available.
Thankfully, there is one property of the problem which we can take advantage of:
a call to `fetchUpdate` which happens *after* `updateFile = Some(newName)` will
read the new file and not the old one, so all we need to do is to wait until all
calls to `fetchUpdate` which were started *before* we mutated `updateFile` have
finished before calling `deleteFile`. It turns out that there exists one
mechanism to do this efficiently: **RCU** (Read-copy-update) which you will implement in the next
section.
## Implementation
### Part 1: Complete the `ThreadMap` implementation
To implement RCU we will need a thread-safe way to associate a value to a
thread: this is the job of `ThreadMap` (defined in `ThreadMap.scala`) which we
implement with a `Map` whose keys are instances of `Thread`
(`Thread.currentThread` can be used to retrieve the instance for the current
thread).
Instead of the usual `forall` method on collections, `ThreadMap` has a
`waitForall` method which will **block** until all entries of the map return
true for the predicate. For example given `val m: ThreadMap[Int]`, if one thread
runs:
```scala
m.setCurrentThread(1)
```
and another thread runs:
```scala
m.waitForall(_ < 0)
```
Then the second thread will be blocked, but if the first thread then runs:
```scala
m.setCurrentThread(-1)
```
The second thread will be immediately unblocked.
Your first task is to **implement all methods in `ThreadMap.scala` whose body is
currently `???`.** Once you're done, the "Part 1" test will pass.
### Part 2: Complete the `RCU` implementation
#### What is RCU ?
The RCU API defines three methods:
```scala
class RCU:
def startRead(): Unit
def stopRead(): Unit
def waitForOldReads(): Unit
```
It has the following contract:
- The first two methods are meant to be used by threads that read shared data:
they must must call `startRead` *before* reading shared data, then call
`stopRead` once they're done reading.
- `waitForOldReaders()` can be called from any thread: this is a blocking method
that only returns when all reads started *before* the call to
`waitForOldReaders()` are stopped (new reads may have started since then).
#### Implementing RCU
To implement RCU we need a way to differentiate reads started before a call to
`waitForOldReaders()` from those started after. To do so we will use *version
numbers*: the RCU version (starting at 0) is stored in `latestVersion`, and for
each *active* reader thread, we remember the value of `latestVersion` *at the time
startRead() was called* in `readersVersion`. Implementing `waitForOldReaders` is
then easy:
1. Increment the RCU version.
2. Wait until there's no active reader associated with a previous version.
**Implement `waitForOldReaders` in `RCU.scala`**, for simplicity you can assume
that `waitForOldReaders` will never be called from multiple threads at once.
Once you're done, the "Part 2" test will pass.
### Part 3: Using RCU in `UpdateServer`
Finally, its time to put our RCU implementation to good use: **complete the
implementation of `fetchUpdate` and `newUpdate`** (defined in `UpdateServer.scala`) by adding calls to
`rcu.startRead()`, `rcu.stopRead()` and `rcu.waitForOldReaders()` where they
need to be to allow multiple calls to `fetchUpdate` and at most one call to
`newUpdate` to be run concurrently. Once you're done, the "Part 3" test will
pass.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment