1 // Gmsh - Copyright (C) 1997-2021 C. Geuzaine, J.-F. Remacle
2 //
3 // See the LICENSE.txt file in the Gmsh root directory for license information.
4 // Please report all issues on https://gitlab.onelab.info/gmsh/gmsh/issues.
5
6 #include "GmshConfig.h"
7 #if !defined(HAVE_NO_STDINT_H)
8 #include <stdint.h>
9 #elif defined(HAVE_NO_INTPTR_T)
10 typedef unsigned long intptr_t;
11 #endif
12 #include "GmshGlobal.h"
13 #include "Context.h"
14 #include "OS.h"
15 #include "StringUtils.h"
16 #include "OpenFile.h"
17 #include "CreateFile.h"
18 #include "Options.h"
19 #include "GModel.h"
20
21 #if defined(HAVE_ONELAB)
22
23 #include "gmshLocalNetworkClient.h"
24 #include "onelabUtils.h"
25
26 #if defined(HAVE_FLTK)
27 #include "FlGui.h"
28 #include "onelabGroup.h"
29 #include "drawContext.h"
30 #endif
31
32 #if defined(HAVE_POST)
33 #include "PView.h"
34 #endif
35
36 #if defined(HAVE_ONELAB_METAMODEL)
37 #include "OnelabClients.h"
38 #include "metamodel.h"
39 #endif
40
41 class onelabGmshServer : public GmshServer {
42 private:
43 onelab::localNetworkClient *_client;
44
45 public:
onelabGmshServer(onelab::localNetworkClient * client)46 onelabGmshServer(onelab::localNetworkClient *client)
47 : GmshServer(), _client(client)
48 {
49 }
~onelabGmshServer()50 ~onelabGmshServer() {}
NonBlockingSystemCall(const std::string & exe,const std::string & args)51 int NonBlockingSystemCall(const std::string &exe, const std::string &args)
52 {
53 return SystemCallExe(exe, args);
54 }
NonBlockingWait(double waitint,double timeout,int socket)55 int NonBlockingWait(double waitint, double timeout, int socket)
56 {
57 double start = TimeOfDay();
58 while(1) {
59 if(timeout > 0 && TimeOfDay() - start > timeout)
60 return 2; // timeout
61 if(_client->getPid() < 0 ||
62 (_client->getExecutable().empty() && !CTX::instance()->solver.listen))
63 return 1; // process has been killed or we stopped listening
64 int ret = 0;
65 #if defined(HAVE_FLTK)
66 if(FlGui::available()) {
67 // if GUI available, check if there is data and return immediately (we
68 // will wait for GUI events later - see below)
69 ret = Select(0, 0, socket);
70 }
71 else {
72 ret = Select(0, waitint * 1e6, socket);
73 }
74 #else
75 ret = Select(0, waitint * 1e6, socket);
76 #endif
77 if(ret == 0) { // nothing available
78 #if defined(HAVE_FLTK)
79 if(FlGui::available()) {
80 if(timeout < 0) {
81 // if asked, refresh the onelab GUI, but no more than every 1/4th of
82 // a second
83 static double lastRefresh = 0.;
84 if(start - lastRefresh > 0.25) {
85 std::vector<onelab::string> ps;
86 onelab::server::instance()->get(ps, "Gmsh/Action");
87 if(ps.size() && ps[0].getValue() == "refresh") {
88 ps[0].setVisible(false);
89 ps[0].setValue("");
90 onelab::server::instance()->set(ps[0]);
91 if(FlGui::available()) onelab_cb(nullptr, (void *)"refresh");
92 }
93 lastRefresh = start;
94 }
95 }
96 // wait at most waitint seconds and respond to FLTK events if ready
97 if(Fl::ready()) FlGui::wait(waitint);
98 }
99 #endif
100 // return to caller (we will be back here soon again)
101 if(timeout < 0) return 3;
102 }
103 else if(ret > 0) {
104 return 0; // data is there!
105 }
106 else {
107 // an error happened
108 _client->setPid(-1);
109 return 1;
110 }
111 }
112 }
LaunchClient()113 int LaunchClient()
114 {
115 std::string sockname;
116 std::ostringstream tmp;
117 static bool first = true;
118 const char *port = strstr(CTX::instance()->solver.socketName.c_str(), ":");
119 if(!port) {
120 // Unix socket
121 tmp << CTX::instance()->homeDir << CTX::instance()->solver.socketName
122 << _client->getId();
123 sockname = FixWindowsPath(tmp.str());
124 }
125 else {
126 // TCP/IP socket
127 if(CTX::instance()->solver.socketName.size() &&
128 CTX::instance()->solver.socketName[0] == ':')
129 tmp << GetHostName(); // prepend hostname if only port number is given
130 tmp << CTX::instance()->solver.socketName;
131 if(atoi(port + 1)) {
132 // if a port number is given we use it for the first client, then we
133 // append the client id so that we can manage several clients at once;
134 // this is ugly but it leads to the expected result if a single client
135 // is instanciated (in general it is recommended to *not* specify the
136 // port number, and let the OS assign a port number automatically)
137 if(!first) {
138 tmp << _client->getId();
139 first = false;
140 }
141 }
142 sockname = tmp.str();
143 }
144
145 std::string exe = FixWindowsPath(_client->getExecutable());
146 std::string args;
147 if(exe.size()) {
148 if(_client->treatExecutableAsFullCommandLine()) {
149 args = exe;
150 exe = "";
151 }
152 else {
153 std::vector<std::string> cl = onelabUtils::getCommandLine(_client);
154 for(std::size_t i = 0; i < cl.size(); i++) args.append(" " + cl[i]);
155 }
156 args.append(" " + _client->getSocketSwitch() + " \"" +
157 _client->getName() + "\" %s");
158 }
159 else {
160 Msg::Info("Listening on socket '%s'", sockname.c_str());
161 }
162
163 int sock;
164 try {
165 sock = Start(exe, args, sockname, CTX::instance()->solver.timeout);
166 } catch(const char *err) {
167 Msg::Error("Abnormal server termination (%s on socket %s)", err,
168 sockname.c_str());
169 sock = -1;
170 }
171
172 return sock;
173 }
174 };
175
tryToGetGmshNumberOption(const std::string & name)176 static std::string tryToGetGmshNumberOption(const std::string &name)
177 {
178 std::string reply;
179 std::string::size_type dot = name.find('.');
180 if(dot != std::string::npos) {
181 double val;
182 if(GmshGetOption(name.substr(0, dot), name.substr(dot + 1), val)) {
183 onelab::number par(name, val);
184 reply = par.toChar();
185 }
186 }
187 return reply;
188 }
189
tryToGetGmshStringOption(const std::string & name)190 static std::string tryToGetGmshStringOption(const std::string &name)
191 {
192 std::string reply;
193 std::string::size_type dot = name.find('.');
194 if(dot != std::string::npos) {
195 std::string val;
196 if(GmshGetOption(name.substr(0, dot), name.substr(dot + 1), val)) {
197 onelab::string par(name, val);
198 reply = par.toChar();
199 }
200 }
201 return reply;
202 }
203
tryToSetGmshNumberOption(onelab::number & p)204 static bool tryToSetGmshNumberOption(onelab::number &p)
205 {
206 const std::string &name = p.getName();
207 std::string::size_type dot = name.find('.');
208 if(dot != std::string::npos) {
209 double val = p.getValue();
210 return GmshSetOption(name.substr(0, dot), name.substr(dot + 1), val);
211 }
212 return false;
213 }
214
tryToSetGmshStringOption(onelab::string & p)215 static bool tryToSetGmshStringOption(onelab::string &p)
216 {
217 const std::string &name = p.getName();
218 std::string::size_type dot = name.find('.');
219 if(dot != std::string::npos) {
220 const std::string &val = p.getValue();
221 return GmshSetOption(name.substr(0, dot), name.substr(dot + 1), val);
222 }
223 return false;
224 }
225
receiveMessage(gmshLocalNetworkClient * master)226 bool gmshLocalNetworkClient::receiveMessage(gmshLocalNetworkClient *master)
227 {
228 // receive a message on the associated GmshServer; 'master' is only used when
229 // creating subclients with GMSH_CONNECT.
230
231 double timer = TimeOfDay();
232
233 if(!getGmshServer()) {
234 Msg::Error("Abnormal server termination (no valid server)");
235 return false;
236 }
237
238 int type, length, swap;
239 if(!getGmshServer()->ReceiveHeader(&type, &length, &swap)) {
240 Msg::Error("Abnormal server termination (did not receive message header)");
241 return false;
242 }
243
244 std::string message(length, ' '), blank = message;
245 if(!getGmshServer()->ReceiveMessage(length, &message[0])) {
246 Msg::Error("Abnormal server termination (did not receive message body)");
247 return false;
248 }
249
250 if(message == blank &&
251 !(type == GmshSocket::GMSH_PROGRESS || type == GmshSocket::GMSH_INFO ||
252 type == GmshSocket::GMSH_WARNING || type == GmshSocket::GMSH_ERROR)) {
253 // we should still allow blank msg strings to be sent
254 Msg::Error(
255 "Abnormal server termination (blank message: client not stopped?)");
256 return false;
257 }
258
259 switch(type) {
260 case GmshSocket::GMSH_START: setPid(atoi(message.c_str())); break;
261 case GmshSocket::GMSH_STOP:
262 setPid(-1);
263 if(getFather()) {
264 std::string reply = getName(); // reply is dummy
265 getFather()->getGmshServer()->SendMessage(GmshSocket::GMSH_STOP,
266 (int)reply.size(), &reply[0]);
267 }
268 break;
269 case GmshSocket::GMSH_PARAMETER:
270 case GmshSocket::GMSH_PARAMETER_WITHOUT_CHOICES:
271 case GmshSocket::GMSH_PARAMETER_UPDATE: {
272 std::string version, ptype, name;
273 onelab::parameter::getInfoFromChar(message, version, ptype, name);
274 if(onelab::parameter::version() != version) {
275 Msg::Error("ONELAB version mismatch (server: %s / client: %s)",
276 onelab::parameter::version().c_str(), version.c_str());
277 }
278 else if(ptype == "number") {
279 onelab::number p;
280 p.fromChar(message);
281 if(!tryToSetGmshNumberOption(p)) {
282 if(type == GmshSocket::GMSH_PARAMETER_WITHOUT_CHOICES) {
283 // append value to any choices already on the server
284 std::vector<onelab::number> par;
285 get(par, name);
286 std::vector<double> c;
287 if(par.size()) c = par[0].getChoices();
288 c.push_back(p.getValue());
289 p.setChoices(c);
290 }
291 if(type == GmshSocket::GMSH_PARAMETER_UPDATE) {
292 std::vector<onelab::number> par;
293 get(par, name);
294 if(par.size()) {
295 onelab::number y = p;
296 p = par[0];
297 onelabUtils::updateNumber(p, y);
298 }
299 }
300 set(p);
301 if(p.getName() == getName() + "/Progress") {
302 #if defined(HAVE_FLTK)
303 if(FlGui::available())
304 FlGui::instance()->setProgress(p.getLabel().c_str(), p.getValue(),
305 p.getMin(), p.getMax());
306 #endif
307 }
308 }
309 }
310 else if(ptype == "string") {
311 onelab::string p;
312 p.fromChar(message);
313 if(!tryToSetGmshStringOption(p)) {
314 if(type == GmshSocket::GMSH_PARAMETER_WITHOUT_CHOICES) {
315 // append value to any choices already on the server
316 std::vector<onelab::string> par;
317 get(par, name);
318 std::vector<std::string> c;
319 if(par.size()) c = par[0].getChoices();
320 c.push_back(p.getValue());
321 p.setChoices(c);
322 }
323 else if(type == GmshSocket::GMSH_PARAMETER_UPDATE) {
324 std::vector<onelab::string> par;
325 get(par, name);
326 if(par.size()) {
327 onelab::string y = p;
328 p = par[0];
329 onelabUtils::updateString(p, y);
330 }
331 }
332 set(p);
333 }
334 }
335 else
336 Msg::Error("Unknown ONELAB parameter type: %s", ptype.c_str());
337 } break;
338 case GmshSocket::GMSH_PARAMETER_QUERY:
339 case GmshSocket::GMSH_PARAMETER_QUERY_WITHOUT_CHOICES: {
340 std::string version, ptype, name, reply;
341 onelab::parameter::getInfoFromChar(message, version, ptype, name);
342 if(onelab::parameter::version() != version) {
343 Msg::Error("ONELAB version mismatch (server: %s / client: %s)",
344 onelab::parameter::version().c_str(), version.c_str());
345 }
346 else if(ptype == "number") {
347 std::vector<onelab::number> par;
348 if(type == GmshSocket::GMSH_PARAMETER_QUERY_WITHOUT_CHOICES)
349 getWithoutChoices(par, name);
350 else
351 get(par, name);
352 if(par.empty())
353 reply = tryToGetGmshNumberOption(name);
354 else
355 reply = par[0].toChar();
356 }
357 else if(ptype == "string") {
358 std::vector<onelab::string> par;
359 if(type == GmshSocket::GMSH_PARAMETER_QUERY_WITHOUT_CHOICES)
360 getWithoutChoices(par, name);
361 else
362 get(par, name);
363 if(par.empty())
364 reply = tryToGetGmshStringOption(name);
365 else
366 reply = par[0].toChar();
367 }
368 else
369 Msg::Error("Unknown ONELAB parameter type in query: %s", ptype.c_str());
370
371 if(reply.size()) {
372 getGmshServer()->SendMessage(GmshSocket::GMSH_PARAMETER,
373 (int)reply.size(), &reply[0]);
374 }
375 else {
376 reply = name;
377 getGmshServer()->SendMessage(GmshSocket::GMSH_PARAMETER_NOT_FOUND,
378 (int)reply.size(), &reply[0]);
379 }
380 } break;
381 case GmshSocket::GMSH_PARAMETER_QUERY_ALL: {
382 std::string version, ptype, name, reply;
383 std::vector<std::string> replies;
384 onelab::parameter::getInfoFromChar(message, version, ptype, name);
385 if(onelab::parameter::version() != version) {
386 Msg::Error("ONELAB version mismatch (server: %s / client: %s)",
387 onelab::parameter::version().c_str(), version.c_str());
388 }
389 else if(ptype == "number") {
390 std::vector<onelab::number> numbers;
391 get(numbers);
392 for(auto it = numbers.begin();
393 it != numbers.end(); it++)
394 replies.push_back((*it).toChar());
395 }
396 else if(ptype == "string") {
397 std::vector<onelab::string> strings;
398 get(strings);
399 for(auto it = strings.begin();
400 it != strings.end(); it++)
401 replies.push_back((*it).toChar());
402 }
403 else
404 Msg::Error("Unknown ONELAB parameter type in query: %s", ptype.c_str());
405
406 for(std::size_t i = 0; i < replies.size(); i++)
407 getGmshServer()->SendMessage(GmshSocket::GMSH_PARAMETER_QUERY_ALL,
408 (int)replies[i].size(), &replies[i][0]);
409 reply = "Sent all ONELAB " + ptype + "s";
410 getGmshServer()->SendMessage(GmshSocket::GMSH_PARAMETER_QUERY_END,
411 (int)reply.size(), &reply[0]);
412 } break;
413 case GmshSocket::GMSH_PARAMETER_CLEAR:
414 clear(message == "*" ? "" : message);
415 break;
416 case GmshSocket::GMSH_PROGRESS:
417 Msg::StatusBar(false, "%s %s", _name.c_str(), message.c_str());
418 break;
419 case GmshSocket::GMSH_INFO:
420 Msg::Direct("Info : %s - %s", _name.c_str(), message.c_str());
421 break;
422 case GmshSocket::GMSH_WARNING:
423 Msg::Warning("%s - %s", _name.c_str(), message.c_str());
424 break;
425 case GmshSocket::GMSH_ERROR:
426 Msg::Error("%s - %s", _name.c_str(), message.c_str());
427 break;
428 case GmshSocket::GMSH_MERGE_FILE:
429 if(CTX::instance()->solver.autoMergeFile) {
430 #if defined(HAVE_FLTK)
431 std::size_t n = PView::list.size();
432 #endif
433 bool changedBeforeMerge = onelab::server::instance()->getChanged("Gmsh");
434 MergePostProcessingFile(message, CTX::instance()->solver.autoShowViews,
435 CTX::instance()->solver.autoShowLastStep, true);
436 onelab::server::instance()->setChanged(changedBeforeMerge, "Gmsh");
437 #if defined(HAVE_FLTK)
438 drawContext::global()->draw();
439 if(FlGui::available() && n != PView::list.size()) {
440 FlGui::instance()->rebuildTree(true);
441 FlGui::instance()->openModule("Post-processing");
442 }
443 #endif
444 }
445 break;
446 case GmshSocket::GMSH_OPEN_PROJECT: OpenProject(message);
447 #if defined(HAVE_FLTK)
448 drawContext::global()->draw();
449 #endif
450 break;
451 case GmshSocket::GMSH_PARSE_STRING: ParseString(message, true);
452 #if defined(HAVE_FLTK)
453 drawContext::global()->draw();
454 #endif
455 break;
456 case GmshSocket::GMSH_SPEED_TEST:
457 Msg::Info("Got %d Mb message in %g seconds", length / 1024 / 1024,
458 TimeOfDay() - timer);
459 break;
460 case GmshSocket::GMSH_VERTEX_ARRAY: {
461 #if defined(HAVE_FLTK)
462 int n = PView::list.size();
463 #endif
464 #if defined(HAVE_POST)
465 PView::fillVertexArray(this, length, &message[0], swap);
466 #endif
467 #if defined(HAVE_FLTK)
468 if(FlGui::available())
469 FlGui::instance()->updateViews(n != (int)PView::list.size(), true);
470 drawContext::global()->draw();
471 #endif
472 } break;
473 case GmshSocket::GMSH_CONNECT: {
474 std::string::size_type first = 0;
475 std::string clientName = onelab::parameter::getNextToken(message, first);
476 std::string command = onelab::parameter::getNextToken(message, first);
477 gmshLocalNetworkClient *subClient =
478 new gmshLocalNetworkClient(clientName, command, "", true);
479 onelabGmshServer *server = new onelabGmshServer(subClient);
480 subClient->setPid(0);
481 onelab::string o(subClient->getName() + "/Action", "compute");
482 o.setVisible(false);
483 o.setNeverChanged(true);
484 onelab::server::instance()->set(o);
485 int sock = server->LaunchClient();
486 if(sock < 0) { // could not establish the connection: aborting
487 server->Shutdown();
488 delete server;
489 Msg::Error("Could not connect client '%s'", subClient->getName().c_str());
490 }
491 else {
492 Msg::StatusBar(true, "Running '%s'...", subClient->getName().c_str());
493 subClient->setGmshServer(server);
494 subClient->setFather(this);
495 master->addClient(subClient);
496 }
497 } break;
498 case GmshSocket::GMSH_OLPARSE: {
499 std::string reply = "unavailable";
500 #if defined(HAVE_ONELAB_METAMODEL)
501 std::string::size_type first = 0;
502 std::string clientName = onelab::parameter::getNextToken(message, first);
503 std::string fullName = onelab::parameter::getNextToken(message, first);
504 preProcess(clientName, fullName); // contrib/onelab/OnelabParser.cpp
505 Msg::Info("Done preprocessing file '%s'", fullName.c_str());
506 reply =
507 onelab::server::instance()->getChanged(clientName) ? "true" : "false";
508 #endif
509 getGmshServer()->SendMessage(GmshSocket::GMSH_OLPARSE,
510 (int)reply.size(), &reply[0]);
511 } break;
512 case GmshSocket::GMSH_CLIENT_CHANGED: {
513 std::string::size_type first = 0;
514 std::string command = onelab::parameter::getNextToken(message, first);
515 std::string name = onelab::parameter::getNextToken(message, first);
516 if(command == "get") {
517 std::string reply =
518 onelab::server::instance()->getChanged(name) ? "true" : "false";
519 getGmshServer()->SendMessage(GmshSocket::GMSH_CLIENT_CHANGED,
520 (int)reply.size(), &reply[0]);
521 }
522 else if(command == "set") {
523 std::string changed = onelab::parameter::getNextToken(message, first);
524 onelab::server::instance()->setChanged(changed == "true" ? 31 : 0, name);
525 }
526 } break;
527 default:
528 Msg::Warning("Received unknown message type (%d)", type);
529 return false;
530 break;
531 }
532
533 return true;
534 }
535
run()536 bool gmshLocalNetworkClient::run()
537 {
538 new_connection:
539 setPid(0); // dummy pid, should be non-negative
540
541 onelabGmshServer *server = new onelabGmshServer(this);
542
543 int sock = server->LaunchClient();
544
545 if(sock < 0) {
546 // could not establish the connection: aborting
547 server->Shutdown();
548 delete server;
549 return false;
550 }
551
552 Msg::StatusBar(true, "Running '%s'...", _name.c_str());
553
554 setGmshServer(server);
555
556 while(1) {
557 if(getExecutable().empty() && !CTX::instance()->solver.listen) {
558 // we stopped listening to the special "Listen" client
559 break;
560 }
561
562 // loop over all the clients (usually only one, but can be more if we
563 // spawned subclients) and check if data is available for one of them
564 bool stop = false, haveData = false;
565 gmshLocalNetworkClient *c = nullptr;
566 std::vector<gmshLocalNetworkClient *> toDelete;
567 for(int i = 0; i < getNumClients(); i++) {
568 c = getClient(i);
569 if(c->getPid() < 0) {
570 if(c == this) { // the "master" client stopped
571 stop = true;
572 break;
573 }
574 else {
575 // this subclient is not active anymore: shut down and delete its
576 // server and mark the client for deletion
577 GmshServer *s = c->getGmshServer();
578 c->setGmshServer(nullptr);
579 c->setFather(nullptr);
580 if(s) {
581 s->Shutdown();
582 delete s;
583 }
584 toDelete.push_back(c);
585 continue;
586 }
587 }
588 GmshServer *s = c->getGmshServer();
589 if(!s) {
590 Msg::Error("Abnormal server termination (no valid server)");
591 stop = true;
592 break;
593 }
594 else {
595 int ret = s->NonBlockingWait(0.0001, -1.);
596 if(ret == 0) { // we have data from this particular client
597 haveData = true;
598 break;
599 }
600 else if(ret == 3) { // pass to the next client
601 continue;
602 }
603 else { // an error occurred
604 stop = true;
605 break;
606 }
607 }
608 }
609 for(std::size_t i = 0; i < toDelete.size(); i++) {
610 removeClient(toDelete[i]);
611 delete toDelete[i];
612 }
613
614 // break the while(1) if the master client has stopped or if we encountered
615 // a problem
616 if(stop) break;
617
618 // if data is available try to get the message from the corresponding
619 // client; break the while(1) if we could not receive the message
620 if(haveData && !c->receiveMessage(this)) break;
621
622 // break the while(1) if the master client has stopped
623 if(c == this && c->getPid() < 0) break;
624 }
625
626 // we are done running the (master) client: delete the servers and the
627 // subclients, if any remain (they should have been deleted already).
628 std::vector<gmshLocalNetworkClient *> toDelete;
629 for(int i = 0; i < getNumClients(); i++) {
630 gmshLocalNetworkClient *c = getClient(i);
631 GmshServer *s = c->getGmshServer();
632 c->setGmshServer(nullptr);
633 c->setFather(nullptr);
634 if(s) {
635 s->Shutdown();
636 delete s;
637 }
638 if(c != this) {
639 if(c->getPid() > 0)
640 Msg::Error("Subclient %s was not stopped correctly",
641 c->getName().c_str());
642 toDelete.push_back(c);
643 }
644 }
645 for(std::size_t i = 0; i < toDelete.size(); i++) {
646 removeClient(toDelete[i]);
647 delete toDelete[i];
648 }
649
650 Msg::StatusBar(true, "Done running '%s'", _name.c_str());
651
652 if(getExecutable().empty()) {
653 Msg::Info("Client disconnected: starting new connection");
654 goto new_connection;
655 }
656
657 return true;
658 }
659
kill()660 bool gmshLocalNetworkClient::kill()
661 {
662 // FIXME: we should kill all the clients in the list
663 if(getPid() > 0) {
664 if(KillProcess(getPid())) {
665 Msg::Info("Killed '%s' (pid %d)", _name.c_str(), getPid());
666 #if defined(HAVE_FLTK)
667 if(FlGui::available()) FlGui::instance()->setProgress("Killed", 0, 0, 0);
668 #endif
669 setPid(-1);
670 return true;
671 }
672 }
673 setPid(-1);
674 return false;
675 }
676
677 #endif
678