1b3384f4a3dee0eaf800e1ae99ccdafa744c6cc6
[msc-thesis1617.git] / arch.itasks.tex
1 The server part of the system is written in \gls{iTasks}. Functions for
2 managing devices, \glspl{Task} and \glspl{SDS} are available. Furthermore, an
3 interactive web application has been created that provides an interactive
4 management console for the \gls{mTask} system. This interface provides
5 functionality to list \glspl{SDS}, add and remove \glspl{Task}, administrate
6 devices and view the state of the system.
7
8 \subsection{Device Storage}
9 Everything that a device encompasses is stored in the \CI{MTaskDevice} record
10 type which is in turn stored in an \gls{SDS}. This includes management for the
11 \glspl{SDS} and \glspl{Task} stored on the device. The \CI{MTaskDevice}
12 definition is shown in Listing~\ref{lst:mtaskdevice} accompanied with the
13 necessary classes and sub types. Devices added to the system must be reachable
14 asynchronously. This implies that the programmer only needs to keep hold of
15 the reference to the device and not the actual device record.
16
17 \begin{lstlisting}[caption={Device type},label={lst:mtaskdevice}]
18 :: Channels :== ([MTaskMSGRecv], [MTaskMSGSend], Bool)
19 :: MTaskDeviceSpec = ... // Also explained in later sections
20 :: MTaskMSGRecv = ... // Message format, explained in later sections
21 :: MTaskMSGSend = ... // Also explained in later sections
22 :: MTaskResource
23 = TCPDevice TCPSettings
24 | SerialDevice TTYSettings
25 | ...
26 :: MTaskDevice =
27 { deviceTask :: Maybe TaskId
28 , deviceError :: Maybe String
29 , deviceChannels :: String
30 , deviceName :: String
31 , deviceState :: BCState
32 , deviceTasks :: [MTaskTask]
33 , deviceResource :: MTaskResource
34 , deviceSpec :: Maybe MTaskDeviceSpec
35 , deviceShares :: [MTaskShare]
36 }
37
38 channels :: MTaskDevice -> Shared Channels
39
40 class MTaskDuplex a where
41 synFun :: a (Shared Channels) -> Task ()
42 \end{lstlisting}
43
44 The \CI{deviceResource} component of the record must implement the
45 \CI{MTaskDuplex} interface that provides a function that launches a \gls{Task}
46 used for synchronizing the channels. The \CI{deviceChannels} field can be used
47 to get the memory \gls{SDS} containing the channels. This field does not
48 contain the channels itself because they update often. The field is used to
49 get a memory \gls{SDS} containing the actual channel data when calling the
50 \CI{channels} function. The \CI{deviceTask} stores the \gls{Task}-id for this
51 \gls{Task} when active so that it can be checked upon. This top-level task has
52 the duty to report exceptions and errors as they are thrown by setting the
53 \CI{deviceError} field. All communication goes via these channels. To send a
54 message to the device, the system just puts it in the channels. Messages sent
55 from the client to the server are also placed in there. In the case of the
56 \gls{TCP} device type, the \gls{Task} is just a simple wrapper around the
57 existing \CI{tcpconnect} function in \gls{iTasks}. In case of a device
58 connected by a serial connection, it uses the newly developed serial port
59 library of \gls{Clean}\footnote{\url{%
60 https://gitlab.science.ru.nl/mlubbers/CleanSerial}}. The implementation and
61 semantics for the \CI{MTaskMSGRecv} and \CI{MTaskMSGSend} types are given in
62 Section~\ref{sec:communication}.
63
64 Besides all the communication information, the record also keeps track of the
65 \glspl{Task} currently on the device, the compiler state (see
66 Section~\ref{sec:compiler}) and the according \glspl{SDS}. Finally, it stores
67 the specification of the device that is received when connecting. All of this
68 is given in Listing~\ref{lst:mtaskdevice}. The definitions of the message
69 format are explained in the following section.
70
71 \subsection{Shares}
72 \Glspl{SDS} on the device can be accessed by both the device and the server.
73 While it would be possible to only store the \glspl{SDS} on the device, this
74 would require a lot of communication because every read operation will then
75 result in sending messages to-and-fro the device. Thus, the \gls{Task}
76 requesting the shared information can just be provided with the synchronized
77 value. As mentioned before, the device has to explicitly publish an update.
78 This has the implication that the server and the client can get out of sync.
79 However, this is by design and well documented. In the current system, an
80 \gls{SDS} can only reside on a single device.
81
82 % Single share per share
83 There are several possible approaches for storing \glspl{SDS} on the server
84 each with their own level of control. A possible way is to --- in the device
85 record --- add a list of references to \gls{iTasks}-\glspl{SDS} that represent
86 the \gls{SDS} on the device. The problem with this is the fact that an
87 \gls{SDS} can become an orphan. The \gls{SDS} is still accessible even when the
88 device is long gone. There is no way of knowing whether the \gls{SDS} is
89 unreachable because of the device being gone, or the \gls{SDS} itself is gone
90 on the device. Accessing the \gls{SDS} happens by calling the \CI{get},
91 \CI{set} and \CI{upd} functions directory on the actual \gls{SDS}.
92
93 % Single share per device
94 Another approach would be to have reference to an \gls{SDS} containing a table
95 of \gls{SDS} values per device. This approach suffers the same orphan problem
96 as before. Accessing a single \gls{SDS} on the device happens by calling the
97 \CI{get}, \CI{set} and \CI{upd} functions on the actual table \gls{SDS} with an
98 applied \CI{mapReadWrite}. Using parametric lenses can circumvent the problem
99 of watchers getting notified for other shares that are written. Error handling
100 is better than the previously mentioned approach because an \gls{SDS} can know
101 whether the \gls{SDS} has really gone because it will not be available anymore
102 in the table. It still does not know whether the device is still available.
103
104 % One share to rule them all
105 Finally, all devices containing all of their \glspl{SDS} in a table could be
106 stored in a single big \gls{SDS}. While the \CI{mapReadWrite} functions require
107 a bit more logic, they can determine the source of the error and act upon it.
108 Also, the parametric lenses must contain more logic. A downside of this
109 approach is that updating a single \gls{SDS} requires an update of the entire
110 object. In practise, this is not a real issue since almost all information can
111 be reused and \gls{SDS} residing on devices are often not updated with a very
112 high frequency.
113
114 \subsection{Parametric Lenses}
115 The type for the parametric lens of the \gls{SDS} containing all devices is
116 \CI{Maybe (MTaskDevice, Int)}. There are several levels of abstraction that
117 have to be introduced. First, the \gls{SDS} responsible for storing the entire
118 list of devices is called the global \gls{SDS}. Secondly, an \gls{SDS} can focus
119 on a single device, such \glspl{SDS} are called local \glspl{SDS}. Finally, an
120 \gls{SDS} can focus on a single \gls{SDS} on a single device. These \glspl{SDS}
121 are called share \glspl{SDS}. Using parametric lenses, the notifications can be
122 directed to only the watchers interested. Moreover, using parametric lenses,
123 the \gls{SDS} can know whether it is updating a single \gls{SDS} on a single
124 device and synchronize the value with the actual device. This means that when
125 writing to a share \gls{SDS} the update is also transformed to messages that
126 are put in the channels of the corresponding device to also notify the device
127 of the update. The \gls{SDS} is tailor-made and uses an actual standard
128 \gls{SDS} that writes to a file or memory as the storage. The tailor-made read
129 and write functions are only used to detect whether it is required to send an
130 update to the actual device.
131
132 Listing~\ref{lst:actualdev} shows the implementation of the big \gls{SDS}. From
133 this \gls{SDS} all other \glspl{SDS} are derived. The following paragraphs show
134 how this is achieved for the global \gls{SDS} local \gls{SDS} and the share
135 \gls{SDS}. In the big \gls{SDS}, reading the value is just a matter of reading
136 the standard \gls{SDS} that serves as the actual storage of the \gls{SDS}. The
137 derived shares will filter the output read accordingly. Writing the share
138 requires some extra work because it might be possible that an actual device has
139 to be notified. First, the actual storage of the \gls{SDS} is written. If the
140 parameter was \CI{Nothing} --- the global \gls{SDS} --- the write operation is
141 done. If the parameter was \CI{Just (d, -1)} --- a local \gls{SDS} --- nothing
142 has to be done as well. The final case is the special case, when the parameter
143 is \CI{Just (d, i)}, this means that the \gls{SDS} was focussed on device
144 \CI{d} and \gls{SDS} \CI{i} and thus it needs to write to it. First it locates
145 the device in the list, followed by the location of the share to check whether
146 is still exists. Finally the actual update messages are added to the device
147 channels using the \CI{sendMessagesIW} function.
148
149 All of the methods share the same \CI{SDSNotifyPred p} which is a function
150 \CI{p -> Bool} and determines for the given \CI{p} whether a notification is
151 required. The predicate function has the \CI{p} of the writer curried in and
152 can determine whether the second argument --- the reader --- needs to be
153 notified. In practice, the reader only needs to be notified when the parameters
154 are exactly the same.
155
156 \begin{lstlisting}[label={lst:actualdev},%
157 caption={Device \gls{SDS}}]
158 ($<) :: a (f a) -> (f b)
159 ($<) a fb = fmap (const a) fb
160
161 deviceStore :: RWShared (Maybe (MTaskDevice, Int)) [MTaskDevice] [MTaskDevice]
162 deviceStore = SDSSource {SDSSource | name="deviceStore", read=realRead, write=realWrite}
163 where
164 realRead :: (Maybe (MTaskDevice,Int)) *IWorld -> (MaybeError TaskException [MTaskDevice], *IWorld)
165 realRead p iw = read realDeviceStore iw
166
167 realWrite :: (Maybe (MTaskDevice,Int)) [MTaskDevice] *IWorld -> (MaybeError TaskException (SDSNotifyPred (Maybe (MTaskDevice,Int))), *IWorld)
168 realWrite mi w iw
169 # (merr, iw) = write w realDeviceStore iw
170 | isError merr || isNothing mi = (merr $> gEq{|*|} mi, iw)
171 # (Just (dev, ident)) = mi
172 | ident == -1 = (merr $> gEq{|*|} mi, iw)
173 = case find ((==)dev) w of
174 Nothing = (Error $ exception "Device lost", iw)
175 Just {deviceShares} = case find (\d->d.identifier == ident) deviceShares of
176 Nothing = (Error $ exception "Share lost", iw)
177 Just s = case sendMessagesIW [MTUpd ident s.MTaskShare.value] dev iw of
178 (Error e, iw) = (Error e, iw)
179 (Ok _, iw) = (Ok $ gEq{|*|} mi, iw)
180
181 realDeviceStore :: Shared [MTaskDevice]
182 realDeviceStore = sharedStore "mTaskDevices" []
183 \end{lstlisting}
184
185 \subsubsection{Global \glspl{SDS}}
186 Accessing the global \gls{SDS} is just a matter of focussing the
187 \CI{deviceStore} to \CI{Nothing}. In this way, \glspl{Task} watching the
188 \gls{SDS} will only be notified if a device is added or removed. The actual
189 code is as follows:
190
191 \begin{lstlisting}[caption={Global \gls{SDS}}]
192 deviceStoreNP :: Shared [MTaskDevice]
193 deviceStoreNP = sdsFocus Nothing deviceStore
194 \end{lstlisting}
195
196 \subsubsection{Local \glspl{SDS}}
197 Accessing a single device can be done using the \CI{deviceShare} function.
198 Since device comparison is shallow, the device that is given is allowed to be
199 an old version. The identification of devices is solely done on the name of the
200 channels and is unique throughout the system. This type of \gls{SDS} will only
201 be notified if the device itself changed. It will not be notified when only a
202 single \gls{SDS} on the device changes. The implementation is as follows:
203
204 \begin{lstlisting}[caption={Local \gls{SDS}}]
205 deviceShare :: MTaskDevice -> Shared MTaskDevice
206 deviceShare d = mapReadWriteError
207 ( \ds->case find ((==)d) ds of
208 Nothing = exception "Device lost"
209 Just d = Ok d)
210 , \w ds->case splitWith ((==)d) ds of
211 ([], _) = Error $ exception "Device lost"
212 ([_:_], ds) = Ok $ Just [w:ds])
213 $ sdsFocus (Just (d, -1)) deviceStore
214 \end{lstlisting}
215
216 \subsubsection{Local-\gls{SDS} specific \glspl{SDS}}
217 A single \gls{SDS} on a single device can be accessed using the \CI{shareShare}
218 function. This function focusses the global \gls{SDS} on a single \gls{SDS}
219 from a single device. It can use old share references in the same fashion as
220 the local \gls{SDS} only treating it as references. It uses the
221 \CI{mapReadWrite} functions to serve the correct part of the information. When
222 a \gls{Task} writes to this \gls{SDS}, the global \gls{SDS} will know this
223 through the parameter and propagate the value to the device.
224
225 \begin{lstlisting}[caption={Local \gls{SDS}}]
226 shareShare :: MTaskDevice MTaskShare -> Shared BCValue
227 shareShare dev share = sdsFocus ()
228 $ mapReadWriteError (read, write)
229 $ sdsFocus (Just (dev, share.identifier))
230 $ deviceStore
231 where
232 read :: [MTaskDevice] -> MaybeError TaskException BCValue
233 read devs = case find ((==)dev) devs of
234 Nothing = exception "Device lost"
235 Just d = case find ((==)share) d.deviceShares of
236 Nothing = exception "Share lost"
237 Just s = Ok s.MTaskShare.value
238
239 write :: BCValue [MTaskDevice] -> MaybeError TaskException (Maybe [MTaskDevice])
240 write val devs = case partition ((==)dev) devs of
241 ([], _) = Error $ exception "Device doesn't exist anymore"
242 ([_,_:_], _) = Error $ exception "Multiple matching devices"
243 ([d=:{deviceShares}], devs) = case partition ((==)share) deviceShares of
244 ([], _) = Error $ exception "Share doesn't exist anymore"
245 ([_,_:_], _) = Error $ exception "Multiple matching shares"
246 ([s], shares) = Ok $ Just [{MTaskDevice | d &
247 deviceShares=[{MTaskShare | s & value=val}:shares]}:devs]
248 \end{lstlisting}