/src/SchemeProcess.cpp

http://github.com/digego/extempore · C++ · 550 lines · 482 code · 25 blank · 43 comment · 70 complexity · 67edb9f409d5112c48e6921020af3ba8 MD5 · raw file

  1. /*
  2. * Copyright (c) 2011, Andrew Sorensen
  3. *
  4. * All rights reserved.
  5. *
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. *
  10. * 1. Redistributions of source code must retain the above copyright notice,
  11. * this list of conditions and the following disclaimer.
  12. *
  13. * 2. Redistributions in binary form must reproduce the above copyright notice,
  14. * this list of conditions and the following disclaimer in the documentation
  15. * and/or other materials provided with the distribution.
  16. *
  17. * Neither the name of the authors nor other contributors may be used to endorse
  18. * or promote products derived from this software without specific prior written
  19. * permission.
  20. *
  21. *
  22. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  23. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  24. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  25. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  26. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  27. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  28. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  29. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  30. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  31. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  32. * POSSIBILITY OF SUCH DAMAGE.
  33. *
  34. */
  35. #include "TaskScheduler.h"
  36. #include "SchemeProcess.h"
  37. #include "SchemeFFI.h"
  38. #include "OSC.h"
  39. #include <iosfwd>
  40. #include <iomanip>
  41. #include <stdexcept>
  42. #include <errno.h>
  43. #include <sys/types.h>
  44. #include <sys/stat.h>
  45. #ifdef _WIN32
  46. #include <ws2tcpip.h>
  47. static void usleep(LONGLONG Us)
  48. {
  49. auto timer(CreateWaitableTimer(NULL, TRUE, NULL));
  50. if (!timer) {
  51. return;
  52. }
  53. LARGE_INTEGER li;
  54. li.QuadPart = -Us * 10;
  55. if (!SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE)) {
  56. CloseHandle(timer);
  57. return;
  58. }
  59. WaitForSingleObject(timer, INFINITE);
  60. CloseHandle(timer);
  61. }
  62. #else
  63. #include <sys/socket.h>
  64. #include <sys/select.h>
  65. #include <netinet/in.h>
  66. #include <netinet/tcp.h>
  67. #include <netdb.h> /* host to IP resolution */
  68. #include <unistd.h>
  69. static int closesocket(int Socket) {
  70. return close(Socket);
  71. }
  72. #endif
  73. #include <stdlib.h>
  74. #include "UNIV.h"
  75. #define EXT_INITEXPR_BUFLEN 1024
  76. static const char TERMINATION_CHAR = 23;
  77. // FD_COPY IS BSD ONLY
  78. #ifndef FD_COPY
  79. #define FD_COPY(f, t) static_cast<void>(*(t) = *(f))
  80. #endif
  81. namespace extemp {
  82. namespace EXTLLVM {
  83. llvm_zone_t* llvm_zone_create(uint64_t);
  84. }
  85. }
  86. #include "EXTLLVM.h"
  87. namespace extemp {
  88. THREAD_LOCAL SchemeProcess* SchemeProcess::sm_current = 0;
  89. const char* SchemeProcess::sm_banner = "\n"
  90. "##########################################\n"
  91. "## ##\n"
  92. "## EXTEMPORE ##\n"
  93. "## ##\n"
  94. "## andrew@moso.com.au ##\n"
  95. "## ##\n"
  96. "## (c) 2005-2015 ##\n"
  97. "## ##\n"
  98. "##########################################\n"
  99. " ################################\n"
  100. " ######################\n"
  101. " ############\n"
  102. " ##\n\n";
  103. SchemeProcess::SchemeProcess(const std::string& LoadPath, const std::string& Name, int ServerPort, bool Banner,
  104. const std::string& InitExpr): m_loadPath(LoadPath), m_name(Name), m_serverPort(ServerPort),
  105. m_banner(Banner), m_initExpr(InitExpr), m_libsLoaded(false), m_guard("scheme_server_guard"),
  106. m_running(true), m_threadTask(&taskTrampoline, this, "SP_task"),
  107. m_threadServer(&serverTrampoline, this, "SP_server")
  108. {
  109. if (m_loadPath[m_loadPath.length() - 1] != '/') {
  110. m_loadPath.push_back('/');
  111. }
  112. m_scheme = scheme_init_new();
  113. m_scheme->m_process = this;
  114. m_defaultZone = extemp::EXTLLVM::llvm_zone_create(50 * 1024 * 1024); // allocate default zone of 50M
  115. strcpy(m_scheme->name, m_name.c_str());
  116. m_maxDuration = m_scheme->call_default_time;
  117. memset(m_schemeOutportString, 0, SCHEME_OUTPORT_STRING_LENGTH);
  118. scheme_set_output_port_string(m_scheme, m_schemeOutportString, m_schemeOutportString +
  119. SCHEME_OUTPORT_STRING_LENGTH - 1);
  120. FILE* initscm = fopen((m_loadPath + "runtime/init.xtm").c_str(), "r");
  121. if (!initscm) {
  122. ascii_error();
  123. printf("ERROR:");
  124. ascii_normal();
  125. std::cout << " could not locate file init.xtm, exiting." << std::endl;
  126. exit(1);
  127. }
  128. scheme_load_file(m_scheme, initscm);
  129. m_serverSocket = socket(AF_INET, SOCK_STREAM, 0);
  130. if (m_serverSocket < 0) {
  131. ascii_error();
  132. printf("ERROR:");
  133. ascii_normal();
  134. std::cout << " could not open Extempore socket" << std::endl;
  135. return;
  136. }
  137. int flag = 1;
  138. setsockopt(m_serverSocket, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&flag), sizeof(flag));
  139. setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&flag), sizeof(flag));
  140. scheme_define(m_scheme, m_scheme->global_env, mk_symbol(m_scheme, "*imp-envs*"), m_scheme->NIL);
  141. scheme_define(m_scheme, m_scheme->global_env, mk_symbol(m_scheme, "*callback*"),
  142. mk_cptr(m_scheme, mk_cb(this, SchemeProcess, schemeCallback)));
  143. m_extemporeCallback = mk_cb(this, SchemeProcess, extemporeCallback);
  144. SchemeFFI::initSchemeFFI(m_scheme);
  145. }
  146. bool SchemeProcess::start(bool subsume)
  147. {
  148. //set socket options
  149. int t_reuse = 1;
  150. setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&t_reuse), sizeof(t_reuse));
  151. struct sockaddr_in address;
  152. memset(&address, 0, sizeof(address));
  153. address.sin_family = AF_INET;
  154. address.sin_port = htons(m_serverPort);
  155. address.sin_addr.s_addr = htonl(INADDR_ANY); //set server's IP
  156. if (bind(m_serverSocket, reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0) {
  157. ascii_error();
  158. printf("ERROR:");
  159. ascii_normal();
  160. std::cout << " server: could not bind server socket on port " << m_serverPort << std::endl;
  161. m_running = false;
  162. return false;
  163. }
  164. if (listen(m_serverSocket, 5) < 0) {
  165. ascii_error();
  166. printf("ERROR:");
  167. ascii_normal();
  168. std::cout << " problem listening to extempore socket" << std::endl;
  169. m_running = false;
  170. return false;
  171. }
  172. if (subsume) {
  173. m_threadTask.setSubsume();
  174. }
  175. m_guard.init();
  176. m_threadServer.start();
  177. m_threadTask.start();
  178. return true;
  179. }
  180. void SchemeProcess::stop()
  181. {
  182. std::cout << "Stop Scheme Interface" << std::endl;
  183. m_running = false;
  184. scheme_deinit(m_scheme);
  185. // TODO: what about sm_current?/name lookup
  186. }
  187. void SchemeProcess::addCallback(TaskI* TaskAdd, SchemeTask::Type Type)
  188. {
  189. EXTMonitor::ScopedLock lock(m_guard, true);
  190. auto currentTime(TaskAdd->getStartTime());
  191. auto duration(TaskAdd->getDuration());
  192. auto task(static_cast<Task<SchemeObj*>*>(TaskAdd));
  193. m_taskQueue.push(SchemeTask(currentTime, duration, task->getArg(), "tmp_label", Type));
  194. }
  195. void SchemeProcess::createSchemeTask(void* Arg, const std::string& Label, SchemeTask::Type Type)
  196. {
  197. EXTMonitor::ScopedLock lock(m_guard, true);
  198. m_taskQueue.push(SchemeTask(extemp::UNIV::TIME, m_maxDuration, Arg, Label, Type));
  199. }
  200. bool SchemeProcess::loadFile(const std::string& File, const std::string& Path)
  201. {
  202. auto fullPath((Path.empty() ? std::string() : (Path + "/")) + File);
  203. auto impscm(fopen(fullPath.c_str(), "r"));
  204. if (!impscm) {
  205. std::cout << "ERROR: Unable to locate file: " << fullPath << std::endl;
  206. return false;
  207. }
  208. scheme_load_file(m_scheme, impscm);
  209. return true;
  210. }
  211. void* SchemeProcess::taskImpl()
  212. {
  213. sm_current = this;
  214. OSC::schemeInit(this);
  215. #ifdef _WIN32
  216. Sleep(1000);
  217. #else
  218. sleep(1); // give time for NSApp etc. to init
  219. #endif
  220. while(!m_running) {
  221. }
  222. loadFile("runtime/scheme.xtm", UNIV::SHARE_DIR);
  223. loadFile("runtime/llvmti.xtm", UNIV::SHARE_DIR);
  224. loadFile("runtime/llvmir.xtm", UNIV::SHARE_DIR);
  225. m_libsLoaded = true;
  226. #ifdef _WIN32
  227. Sleep(2000);
  228. #else
  229. sleep(2); // give time for NSApp etc. to init
  230. #endif
  231. // only load extempore.xtm in primary process
  232. if (m_name == "primary") {
  233. EXTMonitor::ScopedLock lock(m_guard);
  234. m_taskQueue.push(SchemeTask(extemp::UNIV::TIME, m_maxDuration,
  235. new std::string("(sys:compile-init-ll)"), "file_init",
  236. SchemeTask::Type::LOCAL_PROCESS_STRING));
  237. if (extemp::UNIV::EXT_LOADBASE) {
  238. m_taskQueue.push(SchemeTask(extemp::UNIV::TIME, m_maxDuration,
  239. new std::string("(sys:load \"libs/base/base.xtm\" 'quiet)"), "file_init",
  240. SchemeTask::Type::LOCAL_PROCESS_STRING));
  241. } /* else {
  242. m_taskQueue.push(SchemeTask(extemp::UNIV::TIME, m_maxDuration,
  243. new std::string("(sys:compile-init-ll)"), "file_init",
  244. SchemeTask::Type::LOCAL_PROCESS_STRING));
  245. } */
  246. if (!m_initExpr.empty()) {
  247. ascii_text_color(0, 5, 10);
  248. printf("\nEvaluating expression: ");
  249. ascii_normal();
  250. printf("%s\n\n", m_initExpr.c_str());
  251. m_taskQueue.push(SchemeTask(extemp::UNIV::TIME + 1000, 60 * 60 * UNIV::SECOND(),
  252. new std::string(m_initExpr), "file_init", SchemeTask::Type::LOCAL_PROCESS_STRING));
  253. }
  254. }
  255. while (likely(m_running)) {
  256. if (unlikely(m_taskQueue.empty())) {
  257. usleep(1000); // 1 ms
  258. continue;
  259. }
  260. while (likely(!m_taskQueue.empty() && m_running)) {
  261. m_guard.lock();
  262. SchemeTask task = m_taskQueue.front();
  263. m_taskQueue.pop();
  264. m_guard.unlock();
  265. switch (task.getType()) {
  266. case SchemeTask::Type::DESTROY_ENV:
  267. m_scheme->imp_env.erase(reinterpret_cast<pointer>(task.getPtr()));
  268. break;
  269. case SchemeTask::Type::LOCAL_PROCESS_STRING: //string from local process (MIDI, OSC or similar)
  270. {
  271. auto evalString(reinterpret_cast<std::string*>(task.getPtr()));
  272. if (evalString->length() > 2) {
  273. uint64_t now(UNIV::TIME);
  274. scheme_load_string(m_scheme, evalString->c_str(), now, now + task.getMaxDuration());
  275. if (unlikely(m_scheme->retcode)) { //scheme error
  276. resetOutportString();
  277. }
  278. }
  279. delete evalString;
  280. }
  281. break;
  282. case SchemeTask::Type::REPL:
  283. {
  284. auto returnSocket(atoi(task.getLabel().c_str()));
  285. auto evalString(reinterpret_cast<std::string*>(task.getPtr()));
  286. if (evalString->length() > 1) {
  287. std::stringstream ss;
  288. bool write_reply(!!evalString->compare(0, 4, "(ipc"));
  289. if ((*evalString)[evalString->length() - 1] == TERMINATION_CHAR) {
  290. evalString->erase(--evalString->end());
  291. }
  292. uint64_t now(UNIV::TIME);
  293. scheme_load_string(m_scheme, (const char*) evalString->c_str(), now,
  294. now + task.getMaxDuration());
  295. if (unlikely(m_scheme->retcode)) { //scheme error
  296. resetOutportString();
  297. } else {
  298. if (m_banner) {
  299. auto time(UNIV::TIME);
  300. auto hours(time / UNIV::HOUR());
  301. time -= hours * UNIV::HOUR();
  302. auto minutes(time / UNIV::MINUTE());
  303. time -= minutes * UNIV::MINUTE();
  304. auto seconds(time / UNIV::SECOND());
  305. char prompt[24];
  306. sprintf(prompt, "\n[extempore %.2u:%.2u:%.2u]: ", unsigned(hours), unsigned(minutes), unsigned(seconds));
  307. ss << prompt;
  308. }
  309. UNIV::printSchemeCell(m_scheme, ss, m_scheme->value);
  310. if (write_reply) {
  311. auto res(ss.str());
  312. send(returnSocket, res.c_str(), int(res.length() + 1), 0);
  313. }
  314. }
  315. }
  316. delete evalString;
  317. }
  318. break;
  319. case SchemeTask::Type::SCHEME_CALLBACK:
  320. {
  321. auto obj(reinterpret_cast<SchemeObj*>(task.getPtr()));
  322. auto pair(reinterpret_cast<pointer>(obj->getValue()));
  323. auto func(pair_car(pair));
  324. auto args(pair_cdr(pair));
  325. if (is_closure(func) || is_macro(func) || is_continuation(func) || is_proc(func) ||
  326. is_foreign(func)) {
  327. uint64_t now(UNIV::TIME);
  328. scheme_call(m_scheme, func, args, now, now + task.getMaxDuration());
  329. if (unlikely(m_scheme->retcode)) { //scheme error
  330. resetOutportString();
  331. }
  332. } else {
  333. std::stringstream ss;
  334. UNIV::printSchemeCell(m_scheme, ss, pair);
  335. std::cerr << "Bad Closure ... " << ss.str() << " Ignoring callback request " << std::endl;
  336. }
  337. delete obj;
  338. }
  339. break;
  340. case SchemeTask::Type::CALLBACK_SYMBOL: //callback with symbol as char*
  341. {
  342. auto obj(reinterpret_cast<SchemeObj*>(task.getPtr()));
  343. auto symbol(reinterpret_cast<char*>(obj->getValue()));
  344. auto symbolsymbol(mk_symbol(m_scheme, symbol));
  345. auto func(pair_cdr(find_slot_in_env(m_scheme, m_scheme->global_env, symbolsymbol, 1)));
  346. pointer args = m_scheme->NIL;
  347. if (is_closure(func) || is_continuation(func) || is_proc(func) || is_foreign(func)) {
  348. uint64_t now(UNIV::TIME);
  349. scheme_call(m_scheme, func, args, now, now + task.getMaxDuration());
  350. if (m_scheme->retcode) { //scheme error
  351. resetOutportString();
  352. }
  353. } else {
  354. std::stringstream ss;
  355. extemp::UNIV::printSchemeCell(m_scheme, ss, func);
  356. std::cerr << "Bad Closure From Symbol ... " << ss.str() <<
  357. " Ignoring callback request " << std::endl;
  358. }
  359. delete obj;
  360. }
  361. break;
  362. case SchemeTask::Type::EXTEMPORE_CALLBACK:
  363. {
  364. auto s(reinterpret_cast<_llvm_callback_struct_*>(task.getPtr()));
  365. s->fptr(s->dat, s->zone);
  366. }
  367. break;
  368. default:
  369. std::cerr << "ERROR: BAD SchemeTask type!!" << std::endl;
  370. }
  371. }
  372. }
  373. std::cout << "Exiting task thread" << std::endl;
  374. return this;
  375. }
  376. void* SchemeProcess::serverImpl()
  377. {
  378. while (!m_libsLoaded) {
  379. usleep(1000);
  380. }
  381. fd_set readFds;
  382. std::vector<SOCKET> clientSockets;
  383. std::map<SOCKET, std::string> inStrings;
  384. FD_ZERO(&readFds); //zero out open sockets
  385. FD_SET(m_serverSocket, &readFds); //add server socket to open sockets list
  386. int numFds = int(m_serverSocket) + 1;
  387. while (m_running) {
  388. fd_set curReadFds;
  389. FD_COPY(&readFds, &curReadFds);
  390. int res(select(numFds, &curReadFds, NULL, NULL, nullptr));
  391. if (unlikely(res < 0)) { // assumes only one failure
  392. auto iter(clientSockets.begin());
  393. for (; iter != clientSockets.end(); ++iter) {
  394. struct stat buf;
  395. if (fstat(int(*iter), &buf) < 0) {
  396. FD_CLR(*iter, &readFds);
  397. clientSockets.erase(iter);
  398. break;
  399. }
  400. }
  401. ascii_error();
  402. printf("%s SERVER ERROR: %s\n", m_name.c_str(), strerror(errno));
  403. ascii_normal();
  404. continue;
  405. }
  406. if (unlikely(FD_ISSET(m_serverSocket, &curReadFds))) { //check if we have any new accepts on our server socket
  407. sockaddr_in client_address;
  408. socklen_t clientAddressSize(sizeof(client_address));
  409. auto res(accept(m_serverSocket, reinterpret_cast<sockaddr*>(&client_address), &clientAddressSize));
  410. if (unlikely(res < 0)) {
  411. std::cout << "Bad Accept in Server Socket Handling" << std::endl;
  412. continue; //continue on error
  413. }
  414. numFds = int(res) + 1;
  415. FD_SET(res, &readFds); //add new socket to the FD_SET
  416. ascii_info();
  417. printf("INFO:");
  418. ascii_default();
  419. std::cout << " server: accepted new connection to " << m_name << " process" << std::endl;
  420. clientSockets.push_back(res);
  421. inStrings[res].clear();
  422. std::string outString;
  423. if (m_banner) {
  424. outString += sm_banner;
  425. auto time(UNIV::TIME);
  426. auto hours(time / UNIV::HOUR());
  427. time -= hours * UNIV::HOUR();
  428. auto minutes(time / UNIV::MINUTE());
  429. time -= minutes * UNIV::MINUTE();
  430. auto seconds(time / UNIV::SECOND());
  431. char prompt[23];
  432. sprintf(prompt, "[extempore %.2u:%.2u:%.2u]: ", unsigned(hours), unsigned(minutes), unsigned(seconds));
  433. outString += prompt;
  434. } else {
  435. outString += "Welcome to extempore!";
  436. }
  437. send(res, outString.c_str(), int(outString.length() + 1), 0);
  438. continue;
  439. }
  440. for (unsigned index = 0; index < clientSockets.size(); ++index) {
  441. auto sock(clientSockets[index]);
  442. const int BUFLEN = 1024;
  443. char buf[BUFLEN + 1];
  444. if (FD_ISSET(sock, &curReadFds)) { //see if any client sockets have data for us
  445. std::string evalStr;
  446. for (int j = 0; true; j++) { //read from stream in BUFLEN blocks
  447. res = recv(sock, buf, BUFLEN, 0);
  448. if (unlikely(!res)) { //close the socket
  449. FD_CLR(sock, &readFds);
  450. inStrings.erase(sock);
  451. ascii_info();
  452. printf("INFO:");
  453. ascii_default();
  454. std::cout << " server: client disconnected" << std::endl;
  455. clientSockets.erase(clientSockets.begin() + index);
  456. closesocket(sock);
  457. --index;
  458. break;
  459. } else if (unlikely(res < 0)) {
  460. ascii_error();
  461. printf("ERROR:");
  462. ascii_default();
  463. std::cout << " in socket read from extempore process " << strerror(errno) << std::endl;
  464. break;
  465. }
  466. auto& string(inStrings[sock]);
  467. buf[res] = '\0';
  468. string += buf;
  469. if (buf[res - 2] == 0x0d && buf[res - 1] == 0x0a) {
  470. evalStr.swap(string);
  471. break;
  472. }
  473. if (unlikely(j > 1024 *10)) {
  474. ascii_error();
  475. printf("ERROR:");
  476. ascii_default();
  477. std::cout << " eval string too large (no terminator received before 10MB limit)" << std::endl;
  478. ascii_normal();
  479. string.clear();
  480. break;
  481. }
  482. }
  483. if (likely(evalStr != "#break#")) {
  484. std::string::size_type pos = 0;
  485. std::string::size_type end = evalStr.find_first_of('\x0d', pos);
  486. for (; end != std::string::npos; pos = end + 2, end = evalStr.find_first_of('\x0d', pos)) {
  487. EXTMonitor::ScopedLock lock(m_guard, true);
  488. char c[8];
  489. sprintf(c, "%i", int(sock));
  490. std::string* s = new std::string(evalStr.substr(pos, end - pos + 1));
  491. // std::cout << extemp::UNIV::TIME << "> SCHEME TASK WITH SUBEXPR:" << *s << std::endl;
  492. m_taskQueue.push(SchemeTask(extemp::UNIV::TIME, m_maxDuration, s, c, SchemeTask::Type::REPL));
  493. }
  494. }
  495. }
  496. }
  497. }
  498. for (auto sock : clientSockets) {
  499. std::cout << "CLOSE CLIENT-SOCKET" << std::endl;
  500. closesocket(sock);
  501. std::cout << "DONE-CLOSING_CLIENT" << std::endl;
  502. }
  503. if (closesocket(m_serverSocket)) {
  504. std::cerr << "SchemeProcess Error: Error closing server socket" << std::endl;
  505. perror(NULL);
  506. }
  507. std::cout << "Exiting server thread" << std::endl;
  508. return this;
  509. }
  510. SchemeObj::SchemeObj(scheme* Scheme, pointer Values, pointer Env): m_scheme(Scheme), m_values(Values), m_env(Env)
  511. {
  512. if (unlikely(!Env)) {
  513. std::cout << "BANG CRASH SHEBANG" << std::endl;
  514. fflush(stdout);
  515. abort();
  516. }
  517. m_scheme->imp_env.insert(Env);
  518. }
  519. SchemeObj::~SchemeObj()
  520. {
  521. if (likely(m_env)) { // impossible to be null?
  522. m_scheme->m_process->createSchemeTask(m_env, "destroy SchemeObj", SchemeTask::Type::DESTROY_ENV);
  523. }
  524. }
  525. } // namespace imp