Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Hive.Server.Core/ClientCommunicator.cs @ 1139

Last change on this file since 1139 was 1139, checked in by msteinbi, 15 years ago

Implementing Lifecycle Management (#453)

File size: 13.4 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2008 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Text;
26using HeuristicLab.Hive.Contracts.BusinessObjects;
27using HeuristicLab.Hive.Contracts.Interfaces;
28using HeuristicLab.Hive.Contracts;
29using HeuristicLab.Core;
30using HeuristicLab.Hive.Server.Core.InternalInterfaces.DataAccess;
31using System.Resources;
32using System.Reflection;
33using HeuristicLab.Hive.JobBase;
34using System.Runtime.CompilerServices;
35
36namespace HeuristicLab.Hive.Server.Core {
37  /// <summary>
38  /// The ClientCommunicator manages the whole communication with the client
39  /// </summary>
40  public class ClientCommunicator: IClientCommunicator {
41    Dictionary<Guid, DateTime> lastHeartbeats =
42      new Dictionary<Guid,DateTime>();
43
44    IClientAdapter clientAdapter;
45    IJobAdapter jobAdapter;
46    IJobResultsAdapter jobResultAdapter;
47    ILifecycleManager lifecycleManager;
48
49    /// <summary>
50    /// Initialization of the Adapters to the database
51    /// Initialization of Eventhandler for the lifecycle management
52    /// Initialization of lastHearbeats Dictionary
53    /// </summary>
54    public ClientCommunicator() {
55      clientAdapter = ServiceLocator.GetClientAdapter();
56      jobAdapter = ServiceLocator.GetJobAdapter();
57      jobResultAdapter = ServiceLocator.GetJobResultsAdapter();
58      lifecycleManager = ServiceLocator.GetLifecycleManager();
59
60      lifecycleManager.RegisterHeartbeat(
61        new EventHandler(lifecycleManager_OnServerHeartbeat));
62
63      lastHeartbeats = new Dictionary<Guid, DateTime>();
64    }
65
66    /// <summary>
67    /// Check if online clients send their hearbeats
68    /// if not -> set them offline and check if they where calculating a job
69    /// </summary>
70    /// <param name="sender"></param>
71    /// <param name="e"></param>
72    [MethodImpl(MethodImplOptions.Synchronized)]
73    void lifecycleManager_OnServerHeartbeat(object sender, EventArgs e) {
74      List<ClientInfo> allClients = new List<ClientInfo>(clientAdapter.GetAll());
75      List<Job> allJobs = new List<Job>(jobAdapter.GetAll());
76
77      foreach (ClientInfo client in allClients) {
78        if (client.State != State.offline && client.State != State.nullState) {
79          if (!lastHeartbeats.ContainsKey(client.ClientId)) {
80            client.State = State.offline;
81            clientAdapter.Update(client);
82          } else {
83            DateTime lastHbOfClient = lastHeartbeats[client.ClientId];
84            TimeSpan dif = DateTime.Now.Subtract(lastHbOfClient);
85            // check if time between last hearbeat and now is greather than HEARTBEAT_MAX_DIF
86            if (dif.Seconds > ApplicationConstants.HEARTBEAT_MAX_DIF) {
87              // if client calculated jobs, the job must be reset
88              if (client.State == State.calculating) {
89                // check wich job the client was calculating and reset it
90                foreach (Job job in allJobs) {
91                  if (job.Client.ClientId == client.ClientId) {
92                    resetJobsDependingOnResults(job);
93                  }
94                }
95              }
96             
97              // client must be set offline
98              client.State = State.offline;
99              clientAdapter.Update(client);
100              lastHeartbeats.Remove(client.ClientId);
101            }
102          }
103        } else {
104          if (lastHeartbeats.ContainsKey(client.ClientId))
105            lastHeartbeats.Remove(client.ClientId);
106        }
107      }
108    }
109
110    private void resetJobsDependingOnResults(Job job) {
111      List<JobResult> allJobResults = new List<JobResult>(jobResultAdapter.GetAll());
112      JobResult lastJobResult = null;
113      foreach (JobResult jR in allJobResults) {
114        if (jR.Job != null && jR.Job.Id == job.Id) {
115          if (lastJobResult != null) {
116            // if lastJobResult was before the current jobResult the lastJobResult must be updated
117            if ((jR.timestamp.Subtract(lastJobResult.timestamp)).Seconds > 0)
118              lastJobResult = jR;
119          }
120        }
121      }
122      if (lastJobResult != null) {
123        job.Client = null;
124        job.Percentage = lastJobResult.Percentage;
125        job.State = State.idle;
126        job.SerializedJob = lastJobResult.Result;
127      } else {
128        job.Client = null;
129        job.Percentage = 0;
130        job.State = State.idle;
131      }
132      jobAdapter.Update(job);
133    }
134
135    #region IClientCommunicator Members
136
137    /// <summary>
138    /// Login process for the client
139    /// A hearbeat entry is created as well (login is the first hearbeat)
140    /// </summary>
141    /// <param name="clientInfo"></param>
142    /// <returns></returns>
143    [MethodImpl(MethodImplOptions.Synchronized)]
144    public Response Login(ClientInfo clientInfo) {
145      Response response = new Response();
146
147      if (lastHeartbeats.ContainsKey(clientInfo.ClientId)) {
148        lastHeartbeats[clientInfo.ClientId] = DateTime.Now;
149      } else {
150        lastHeartbeats.Add(clientInfo.ClientId, DateTime.Now);
151      }
152
153      ICollection<ClientInfo> allClients = clientAdapter.GetAll();
154      ClientInfo client = clientAdapter.GetById(clientInfo.ClientId);
155      if (client != null && client.State != State.offline && client.State != State.nullState) {
156        response.Success = false;
157        response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_LOGIN_USER_ALLREADY_ONLINE;
158        return response;
159      }
160      clientInfo.State = State.idle;
161      clientAdapter.Update(clientInfo);
162      response.Success = true;
163      response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_LOGIN_SUCCESS;
164
165      return response;
166    }
167
168    /// <summary>
169    /// The client has to send regulary heartbeats
170    /// this hearbeats will be stored in the heartbeats dictionary
171    /// check if there is work for the client and send the client a response if he should pull a job
172    /// </summary>
173    /// <param name="hbData"></param>
174    /// <returns></returns>
175    [MethodImpl(MethodImplOptions.Synchronized)]
176    public ResponseHB SendHeartBeat(HeartBeatData hbData) {
177      ResponseHB response = new ResponseHB();
178
179      response.ActionRequest = new List<MessageContainer>();
180      if (clientAdapter.GetById(hbData.ClientId).State == State.offline ||
181          clientAdapter.GetById(hbData.ClientId).State == State.nullState) {
182        response.Success = false;
183        response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_USER_NOT_LOGGED_IN;
184        response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.NoMessage));
185        return response;
186      }
187
188      if (lastHeartbeats.ContainsKey(hbData.ClientId)) {
189        lastHeartbeats[hbData.ClientId] = DateTime.Now;
190      } else {
191        lastHeartbeats.Add(hbData.ClientId, DateTime.Now);
192      }
193
194      response.Success = true;
195      response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_HARDBEAT_RECEIVED;
196      List<Job> allOfflineJobs = new List<Job>(jobAdapter.GetJobsByState(State.offline));
197      if (allOfflineJobs.Count > 0 && hbData.freeCores > 0)
198        response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.FetchJob));
199      else
200        response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.NoMessage));
201
202      if (hbData.jobProgress != null) {
203        foreach (KeyValuePair<long, double> jobProgress in hbData.jobProgress) {
204          Job curJob = jobAdapter.GetById(jobProgress.Key);
205          curJob.Percentage = jobProgress.Value;
206          jobAdapter.Update(curJob);
207        }
208      }
209
210      return response;
211    }
212   
213    /// <summary>
214    /// if the client asked to pull a job he calls this method
215    /// the server selects a job and sends it to the client
216    /// </summary>
217    /// <param name="clientId"></param>
218    /// <returns></returns>
219    [MethodImpl(MethodImplOptions.Synchronized)]
220    public ResponseJob PullJob(Guid clientId) {
221      ResponseJob response = new ResponseJob();
222      lock (this) {
223        LinkedList<Job> allOfflineJobs = new LinkedList<Job>(jobAdapter.GetJobsByState(State.offline));
224        if (allOfflineJobs != null && allOfflineJobs.Count > 0) {
225          Job job2Calculate = allOfflineJobs.First.Value;
226          job2Calculate.State = State.calculating;
227          job2Calculate.Client = clientAdapter.GetById(clientId);
228          response.Job = job2Calculate;
229          jobAdapter.Update(job2Calculate);
230          response.Success = true;
231          response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_JOB_PULLED;
232          return response;
233        }
234      }
235      response.Success = true;
236      response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_NO_JOBS_LEFT;
237      return response;
238    }
239
240    /// <summary>
241    /// the client can send job results during calculating
242    /// and will send a final job result when he finished calculating
243    /// these job results will be stored in the database
244    /// </summary>
245    /// <param name="clientId"></param>
246    /// <param name="jobId"></param>
247    /// <param name="result"></param>
248    /// <param name="exception"></param>
249    /// <param name="finished"></param>
250    /// <returns></returns>
251    [MethodImpl(MethodImplOptions.Synchronized)]
252    public ResponseResultReceived SendJobResult(Guid clientId,
253      long jobId,
254      byte[] result,
255      double percentage,
256      Exception exception, 
257      bool finished) {
258      ResponseResultReceived response = new ResponseResultReceived();
259      ClientInfo client =
260        clientAdapter.GetById(clientId);
261
262      Job job =
263        jobAdapter.GetById(jobId);
264
265      if (job.Client == null)    {
266        response.Success = false;
267        response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_JOB_IS_NOT_BEEING_CALCULATED;
268        return response;
269      }
270      if (job.Client.ClientId != clientId) {
271        response.Success = false;
272        response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_WRONG_CLIENT_FOR_JOB;
273        return response;
274      }
275      if (job == null) {
276        response.Success = false;
277        response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_NO_JOB_WITH_THIS_ID;
278        return response;
279      }
280      if (job.State != State.calculating) {
281        response.Success = false;
282        response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_WRONG_JOB_STATE;
283        return response;
284      }
285      job.SerializedJob = result;
286      job.Percentage = percentage;
287
288      if (finished) {
289        job.State = State.finished;
290        jobAdapter.Update(job);
291
292        List<JobResult> jobResults = new List<JobResult>(jobResultAdapter.GetResultsOf(job));
293        foreach (JobResult currentResult in jobResults)
294          jobResultAdapter.Delete(currentResult);
295      }
296
297      JobResult jobResult =
298        new JobResult();
299      jobResult.Client = client;
300      jobResult.Job = job;
301      jobResult.Result = result;
302      jobResult.Percentage = percentage;
303      jobResult.Exception = exception;
304
305      jobResultAdapter.Update(jobResult);
306      jobAdapter.Update(job);
307
308      response.Success = true;
309      response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_JOBRESULT_RECEIVED;
310      response.JobId = jobId;
311      response.finished = finished;
312
313      return response;
314    }
315
316    /// <summary>
317    /// when a client logs out the state will be set
318    /// and the entry in the last hearbeats dictionary will be removed
319    /// </summary>
320    /// <param name="clientId"></param>
321    /// <returns></returns>
322    [MethodImpl(MethodImplOptions.Synchronized)]                       
323    public Response Logout(Guid clientId) {
324      Response response = new Response();
325
326      if (lastHeartbeats.ContainsKey(clientId))
327        lastHeartbeats.Remove(clientId);
328
329      ClientInfo client = clientAdapter.GetById(clientId);
330      if (client == null) {
331        response.Success = false;
332        response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_LOGOUT_CLIENT_NOT_REGISTERED;
333        return response;
334      }
335      List<Job> allJobs = new List<Job>(jobAdapter.GetAll());
336      if (client.State == State.calculating) {
337        // check wich job the client was calculating and reset it
338        foreach (Job job in allJobs) {
339          if (job.Client.ClientId == client.ClientId) {
340            resetJobsDependingOnResults(job);
341          }
342        }
343      }
344
345      client.State = State.offline;
346      clientAdapter.Update(client);
347
348      response.Success = true;
349      response.StatusMessage = ApplicationConstants.RESPONSE_COMMUNICATOR_LOGOUT_SUCCESS;
350     
351      return response;
352    }
353
354    #endregion
355  }
356}
Note: See TracBrowser for help on using the repository browser.