eventmachine-eventmachine 0.12.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/Rakefile +169 -0
  2. data/docs/COPYING +60 -0
  3. data/docs/ChangeLog +183 -0
  4. data/docs/DEFERRABLES +138 -0
  5. data/docs/EPOLL +141 -0
  6. data/docs/GNU +281 -0
  7. data/docs/INSTALL +15 -0
  8. data/docs/KEYBOARD +38 -0
  9. data/docs/LEGAL +25 -0
  10. data/docs/LIGHTWEIGHT_CONCURRENCY +72 -0
  11. data/docs/PURE_RUBY +77 -0
  12. data/docs/README +74 -0
  13. data/docs/RELEASE_NOTES +96 -0
  14. data/docs/SMTP +9 -0
  15. data/docs/SPAWNED_PROCESSES +93 -0
  16. data/docs/TODO +10 -0
  17. data/ext/binder.cpp +126 -0
  18. data/ext/binder.h +48 -0
  19. data/ext/cmain.cpp +530 -0
  20. data/ext/cplusplus.cpp +172 -0
  21. data/ext/ed.cpp +1473 -0
  22. data/ext/ed.h +361 -0
  23. data/ext/em.cpp +1895 -0
  24. data/ext/em.h +170 -0
  25. data/ext/emwin.cpp +300 -0
  26. data/ext/emwin.h +94 -0
  27. data/ext/epoll.cpp +26 -0
  28. data/ext/epoll.h +25 -0
  29. data/ext/eventmachine.h +90 -0
  30. data/ext/eventmachine_cpp.h +94 -0
  31. data/ext/extconf.rb +150 -0
  32. data/ext/files.cpp +94 -0
  33. data/ext/files.h +65 -0
  34. data/ext/kb.cpp +368 -0
  35. data/ext/page.cpp +107 -0
  36. data/ext/page.h +51 -0
  37. data/ext/pipe.cpp +327 -0
  38. data/ext/project.h +119 -0
  39. data/ext/rubymain.cpp +683 -0
  40. data/ext/sigs.cpp +89 -0
  41. data/ext/sigs.h +32 -0
  42. data/ext/ssl.cpp +408 -0
  43. data/ext/ssl.h +86 -0
  44. data/java/src/com/rubyeventmachine/Application.java +196 -0
  45. data/java/src/com/rubyeventmachine/Connection.java +74 -0
  46. data/java/src/com/rubyeventmachine/ConnectionFactory.java +37 -0
  47. data/java/src/com/rubyeventmachine/DefaultConnectionFactory.java +46 -0
  48. data/java/src/com/rubyeventmachine/EmReactor.java +408 -0
  49. data/java/src/com/rubyeventmachine/EmReactorException.java +40 -0
  50. data/java/src/com/rubyeventmachine/EventableChannel.java +57 -0
  51. data/java/src/com/rubyeventmachine/EventableDatagramChannel.java +171 -0
  52. data/java/src/com/rubyeventmachine/EventableSocketChannel.java +244 -0
  53. data/java/src/com/rubyeventmachine/PeriodicTimer.java +38 -0
  54. data/java/src/com/rubyeventmachine/Timer.java +54 -0
  55. data/java/src/com/rubyeventmachine/tests/ApplicationTest.java +108 -0
  56. data/java/src/com/rubyeventmachine/tests/ConnectTest.java +124 -0
  57. data/java/src/com/rubyeventmachine/tests/EMTest.java +80 -0
  58. data/java/src/com/rubyeventmachine/tests/TestDatagrams.java +53 -0
  59. data/java/src/com/rubyeventmachine/tests/TestServers.java +74 -0
  60. data/java/src/com/rubyeventmachine/tests/TestTimers.java +89 -0
  61. data/lib/em/deferrable.rb +208 -0
  62. data/lib/em/eventable.rb +39 -0
  63. data/lib/em/future.rb +62 -0
  64. data/lib/em/messages.rb +66 -0
  65. data/lib/em/processes.rb +68 -0
  66. data/lib/em/spawnable.rb +88 -0
  67. data/lib/em/streamer.rb +112 -0
  68. data/lib/eventmachine.rb +1763 -0
  69. data/lib/eventmachine_version.rb +31 -0
  70. data/lib/evma.rb +32 -0
  71. data/lib/evma/callback.rb +32 -0
  72. data/lib/evma/container.rb +75 -0
  73. data/lib/evma/factory.rb +77 -0
  74. data/lib/evma/protocol.rb +87 -0
  75. data/lib/evma/reactor.rb +48 -0
  76. data/lib/jeventmachine.rb +137 -0
  77. data/lib/pr_eventmachine.rb +1011 -0
  78. data/lib/protocols/buftok.rb +127 -0
  79. data/lib/protocols/header_and_content.rb +129 -0
  80. data/lib/protocols/httpcli2.rb +794 -0
  81. data/lib/protocols/httpclient.rb +270 -0
  82. data/lib/protocols/line_and_text.rb +122 -0
  83. data/lib/protocols/linetext2.rb +163 -0
  84. data/lib/protocols/postgres.rb +261 -0
  85. data/lib/protocols/saslauth.rb +179 -0
  86. data/lib/protocols/smtpclient.rb +308 -0
  87. data/lib/protocols/smtpserver.rb +556 -0
  88. data/lib/protocols/stomp.rb +130 -0
  89. data/lib/protocols/tcptest.rb +57 -0
  90. data/tasks/cpp.rake +77 -0
  91. data/tasks/project.rake +78 -0
  92. data/tasks/tests.rake +192 -0
  93. data/tests/test_attach.rb +66 -0
  94. data/tests/test_basic.rb +231 -0
  95. data/tests/test_defer.rb +47 -0
  96. data/tests/test_epoll.rb +161 -0
  97. data/tests/test_errors.rb +82 -0
  98. data/tests/test_eventables.rb +78 -0
  99. data/tests/test_exc.rb +58 -0
  100. data/tests/test_futures.rb +214 -0
  101. data/tests/test_hc.rb +218 -0
  102. data/tests/test_httpclient.rb +215 -0
  103. data/tests/test_httpclient2.rb +133 -0
  104. data/tests/test_kb.rb +61 -0
  105. data/tests/test_ltp.rb +192 -0
  106. data/tests/test_ltp2.rb +320 -0
  107. data/tests/test_next_tick.rb +102 -0
  108. data/tests/test_processes.rb +56 -0
  109. data/tests/test_pure.rb +129 -0
  110. data/tests/test_running.rb +47 -0
  111. data/tests/test_sasl.rb +74 -0
  112. data/tests/test_send_file.rb +245 -0
  113. data/tests/test_servers.rb +80 -0
  114. data/tests/test_smtpclient.rb +81 -0
  115. data/tests/test_smtpserver.rb +93 -0
  116. data/tests/test_spawn.rb +329 -0
  117. data/tests/test_ssl_args.rb +68 -0
  118. data/tests/test_timers.rb +146 -0
  119. data/tests/test_ud.rb +43 -0
  120. data/tests/testem.rb +31 -0
  121. metadata +197 -0
data/ext/files.cpp ADDED
@@ -0,0 +1,94 @@
1
+ /*****************************************************************************
2
+
3
+ $Id$
4
+
5
+ File: files.cpp
6
+ Date: 26Aug06
7
+
8
+ Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
9
+ Gmail: blackhedd
10
+
11
+ This program is free software; you can redistribute it and/or modify
12
+ it under the terms of either: 1) the GNU General Public License
13
+ as published by the Free Software Foundation; either version 2 of the
14
+ License, or (at your option) any later version; or 2) Ruby's License.
15
+
16
+ See the file COPYING for complete licensing information.
17
+
18
+ *****************************************************************************/
19
+
20
+ #include "project.h"
21
+
22
+
23
+ /******************************************
24
+ FileStreamDescriptor::FileStreamDescriptor
25
+ ******************************************/
26
+
27
+ FileStreamDescriptor::FileStreamDescriptor (int fd, EventMachine_t *em):
28
+ EventableDescriptor (fd, em),
29
+ OutboundDataSize (0)
30
+ {
31
+ cerr << "#####";
32
+ }
33
+
34
+
35
+ /*******************************************
36
+ FileStreamDescriptor::~FileStreamDescriptor
37
+ *******************************************/
38
+
39
+ FileStreamDescriptor::~FileStreamDescriptor()
40
+ {
41
+ // Run down any stranded outbound data.
42
+ for (size_t i=0; i < OutboundPages.size(); i++)
43
+ OutboundPages[i].Free();
44
+ }
45
+
46
+
47
+ /**************************
48
+ FileStreamDescriptor::Read
49
+ **************************/
50
+
51
+ void FileStreamDescriptor::Read()
52
+ {
53
+ }
54
+
55
+ /***************************
56
+ FileStreamDescriptor::Write
57
+ ***************************/
58
+
59
+ void FileStreamDescriptor::Write()
60
+ {
61
+ }
62
+
63
+
64
+ /*******************************
65
+ FileStreamDescriptor::Heartbeat
66
+ *******************************/
67
+
68
+ void FileStreamDescriptor::Heartbeat()
69
+ {
70
+ }
71
+
72
+
73
+ /***********************************
74
+ FileStreamDescriptor::SelectForRead
75
+ ***********************************/
76
+
77
+ bool FileStreamDescriptor::SelectForRead()
78
+ {
79
+ cerr << "R?";
80
+ return false;
81
+ }
82
+
83
+
84
+ /************************************
85
+ FileStreamDescriptor::SelectForWrite
86
+ ************************************/
87
+
88
+ bool FileStreamDescriptor::SelectForWrite()
89
+ {
90
+ cerr << "W?";
91
+ return false;
92
+ }
93
+
94
+
data/ext/files.h ADDED
@@ -0,0 +1,65 @@
1
+ /*****************************************************************************
2
+
3
+ $Id$
4
+
5
+ File: files.h
6
+ Date: 26Aug06
7
+
8
+ Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
9
+ Gmail: blackhedd
10
+
11
+ This program is free software; you can redistribute it and/or modify
12
+ it under the terms of either: 1) the GNU General Public License
13
+ as published by the Free Software Foundation; either version 2 of the
14
+ License, or (at your option) any later version; or 2) Ruby's License.
15
+
16
+ See the file COPYING for complete licensing information.
17
+
18
+ *****************************************************************************/
19
+
20
+
21
+ #ifndef __FileStreamDescriptor__H_
22
+ #define __FileStreamDescriptor__H_
23
+
24
+
25
+
26
+ /**************************
27
+ class FileStreamDescriptor
28
+ **************************/
29
+
30
+ class FileStreamDescriptor: public EventableDescriptor
31
+ {
32
+ public:
33
+ FileStreamDescriptor (int, EventMachine_t*);
34
+ virtual ~FileStreamDescriptor();
35
+
36
+ virtual void Read();
37
+ virtual void Write();
38
+ virtual void Heartbeat();
39
+
40
+ virtual bool SelectForRead();
41
+ virtual bool SelectForWrite();
42
+
43
+ // Do we have any data to write? This is used by ShouldDelete.
44
+ virtual int GetOutboundDataSize() {return OutboundDataSize;}
45
+
46
+ protected:
47
+ struct OutboundPage {
48
+ OutboundPage (const char *b, int l, int o=0): Buffer(b), Length(l), Offset(o) {}
49
+ void Free() {if (Buffer) free ((char*)Buffer); }
50
+ const char *Buffer;
51
+ int Length;
52
+ int Offset;
53
+ };
54
+
55
+ protected:
56
+ deque<OutboundPage> OutboundPages;
57
+ int OutboundDataSize;
58
+
59
+ private:
60
+
61
+ };
62
+
63
+
64
+ #endif // __FileStreamDescriptor__H_
65
+
data/ext/kb.cpp ADDED
@@ -0,0 +1,368 @@
1
+ /*****************************************************************************
2
+
3
+ $Id$
4
+
5
+ File: kb.cpp
6
+ Date: 24Aug07
7
+
8
+ Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
9
+ Gmail: blackhedd
10
+
11
+ This program is free software; you can redistribute it and/or modify
12
+ it under the terms of either: 1) the GNU General Public License
13
+ as published by the Free Software Foundation; either version 2 of the
14
+ License, or (at your option) any later version; or 2) Ruby's License.
15
+
16
+ See the file COPYING for complete licensing information.
17
+
18
+ *****************************************************************************/
19
+
20
+ #include "project.h"
21
+
22
+
23
+ /**************************************
24
+ KeyboardDescriptor::KeyboardDescriptor
25
+ **************************************/
26
+
27
+ KeyboardDescriptor::KeyboardDescriptor (EventMachine_t *parent_em):
28
+ EventableDescriptor (0, parent_em),
29
+ bReadAttemptedAfterClose (false),
30
+ LastIo (gCurrentLoopTime),
31
+ InactivityTimeout (0)
32
+ {
33
+ #ifdef HAVE_EPOLL
34
+ EpollEvent.events = EPOLLIN;
35
+ #endif
36
+ #ifdef HAVE_KQUEUE
37
+ MyEventMachine->ArmKqueueReader (this);
38
+ #endif
39
+ }
40
+
41
+
42
+ /***************************************
43
+ KeyboardDescriptor::~KeyboardDescriptor
44
+ ***************************************/
45
+
46
+ KeyboardDescriptor::~KeyboardDescriptor()
47
+ {
48
+ }
49
+
50
+
51
+ /*************************
52
+ KeyboardDescriptor::Write
53
+ *************************/
54
+
55
+ void KeyboardDescriptor::Write()
56
+ {
57
+ // Why are we here?
58
+ throw std::runtime_error ("bad code path in keyboard handler");
59
+ }
60
+
61
+
62
+ /*****************************
63
+ KeyboardDescriptor::Heartbeat
64
+ *****************************/
65
+
66
+ void KeyboardDescriptor::Heartbeat()
67
+ {
68
+ // no-op
69
+ }
70
+
71
+
72
+ /************************
73
+ KeyboardDescriptor::Read
74
+ ************************/
75
+
76
+ void KeyboardDescriptor::Read()
77
+ {
78
+ char c;
79
+ read (GetSocket(), &c, 1);
80
+ if (EventCallback)
81
+ (*EventCallback)(GetBinding().c_str(), EM_CONNECTION_READ, &c, 1);
82
+ }
83
+
84
+
85
+
86
+
87
+ #if 0
88
+ /******************************
89
+ PipeDescriptor::PipeDescriptor
90
+ ******************************/
91
+
92
+ PipeDescriptor::PipeDescriptor (int fd, pid_t subpid, EventMachine_t *parent_em):
93
+ EventableDescriptor (fd, parent_em),
94
+ bReadAttemptedAfterClose (false),
95
+ LastIo (gCurrentLoopTime),
96
+ InactivityTimeout (0),
97
+ OutboundDataSize (0),
98
+ SubprocessPid (subpid)
99
+ {
100
+ #ifdef HAVE_EPOLL
101
+ EpollEvent.events = EPOLLIN;
102
+ #endif
103
+ }
104
+
105
+
106
+ /*******************************
107
+ PipeDescriptor::~PipeDescriptor
108
+ *******************************/
109
+
110
+ PipeDescriptor::~PipeDescriptor()
111
+ {
112
+ // Run down any stranded outbound data.
113
+ for (size_t i=0; i < OutboundPages.size(); i++)
114
+ OutboundPages[i].Free();
115
+
116
+ /* As a virtual destructor, we come here before the base-class
117
+ * destructor that closes our file-descriptor.
118
+ * We have to make sure the subprocess goes down (if it's not
119
+ * already down) and we have to reap the zombie.
120
+ *
121
+ * This implementation is PROVISIONAL and will surely be improved.
122
+ * The intention here is that we never block, hence the highly
123
+ * undesirable sleeps. But if we can't reap the subprocess even
124
+ * after sending it SIGKILL, then something is wrong and we
125
+ * throw a fatal exception, which is also not something we should
126
+ * be doing.
127
+ *
128
+ * Eventually the right thing to do will be to have the reactor
129
+ * core respond to SIGCHLD by chaining a handler on top of the
130
+ * one Ruby may have installed, and dealing with a list of dead
131
+ * children that are pending cleanup.
132
+ *
133
+ * Since we want to have a signal processor integrated into the
134
+ * client-visible API, let's wait until that is done before cleaning
135
+ * this up.
136
+ */
137
+
138
+ struct timespec req = {0, 10000000};
139
+ kill (SubprocessPid, SIGTERM);
140
+ nanosleep (&req, NULL);
141
+ if (waitpid (SubprocessPid, NULL, WNOHANG) == 0) {
142
+ kill (SubprocessPid, SIGKILL);
143
+ nanosleep (&req, NULL);
144
+ if (waitpid (SubprocessPid, NULL, WNOHANG) == 0)
145
+ throw std::runtime_error ("unable to reap subprocess");
146
+ }
147
+ }
148
+
149
+
150
+
151
+ /********************
152
+ PipeDescriptor::Read
153
+ ********************/
154
+
155
+ void PipeDescriptor::Read()
156
+ {
157
+ int sd = GetSocket();
158
+ if (sd == INVALID_SOCKET) {
159
+ assert (!bReadAttemptedAfterClose);
160
+ bReadAttemptedAfterClose = true;
161
+ return;
162
+ }
163
+
164
+ LastIo = gCurrentLoopTime;
165
+
166
+ int total_bytes_read = 0;
167
+ char readbuffer [16 * 1024];
168
+
169
+ for (int i=0; i < 10; i++) {
170
+ // Don't read just one buffer and then move on. This is faster
171
+ // if there is a lot of incoming.
172
+ // But don't read indefinitely. Give other sockets a chance to run.
173
+ // NOTICE, we're reading one less than the buffer size.
174
+ // That's so we can put a guard byte at the end of what we send
175
+ // to user code.
176
+ // Use read instead of recv, which on Linux gives a "socket operation
177
+ // on nonsocket" error.
178
+
179
+
180
+ int r = read (sd, readbuffer, sizeof(readbuffer) - 1);
181
+ //cerr << "<R:" << r << ">";
182
+
183
+ if (r > 0) {
184
+ total_bytes_read += r;
185
+ LastRead = gCurrentLoopTime;
186
+
187
+ // Add a null-terminator at the the end of the buffer
188
+ // that we will send to the callback.
189
+ // DO NOT EVER CHANGE THIS. We want to explicitly allow users
190
+ // to be able to depend on this behavior, so they will have
191
+ // the option to do some things faster. Additionally it's
192
+ // a security guard against buffer overflows.
193
+ readbuffer [r] = 0;
194
+ if (EventCallback)
195
+ (*EventCallback)(GetBinding().c_str(), EM_CONNECTION_READ, readbuffer, r);
196
+ }
197
+ else if (r == 0) {
198
+ break;
199
+ }
200
+ else {
201
+ // Basically a would-block, meaning we've read everything there is to read.
202
+ break;
203
+ }
204
+
205
+ }
206
+
207
+
208
+ if (total_bytes_read == 0) {
209
+ // If we read no data on a socket that selected readable,
210
+ // it generally means the other end closed the connection gracefully.
211
+ ScheduleClose (false);
212
+ //bCloseNow = true;
213
+ }
214
+
215
+ }
216
+
217
+ /*********************
218
+ PipeDescriptor::Write
219
+ *********************/
220
+
221
+ void PipeDescriptor::Write()
222
+ {
223
+ int sd = GetSocket();
224
+ assert (sd != INVALID_SOCKET);
225
+
226
+ LastIo = gCurrentLoopTime;
227
+ char output_buffer [16 * 1024];
228
+ size_t nbytes = 0;
229
+
230
+ while ((OutboundPages.size() > 0) && (nbytes < sizeof(output_buffer))) {
231
+ OutboundPage *op = &(OutboundPages[0]);
232
+ if ((nbytes + op->Length - op->Offset) < sizeof (output_buffer)) {
233
+ memcpy (output_buffer + nbytes, op->Buffer + op->Offset, op->Length - op->Offset);
234
+ nbytes += (op->Length - op->Offset);
235
+ op->Free();
236
+ OutboundPages.pop_front();
237
+ }
238
+ else {
239
+ int len = sizeof(output_buffer) - nbytes;
240
+ memcpy (output_buffer + nbytes, op->Buffer + op->Offset, len);
241
+ op->Offset += len;
242
+ nbytes += len;
243
+ }
244
+ }
245
+
246
+ // We should never have gotten here if there were no data to write,
247
+ // so assert that as a sanity check.
248
+ // Don't bother to make sure nbytes is less than output_buffer because
249
+ // if it were we probably would have crashed already.
250
+ assert (nbytes > 0);
251
+
252
+ assert (GetSocket() != INVALID_SOCKET);
253
+ int bytes_written = write (GetSocket(), output_buffer, nbytes);
254
+
255
+ if (bytes_written > 0) {
256
+ OutboundDataSize -= bytes_written;
257
+ if ((size_t)bytes_written < nbytes) {
258
+ int len = nbytes - bytes_written;
259
+ char *buffer = (char*) malloc (len + 1);
260
+ if (!buffer)
261
+ throw std::runtime_error ("bad alloc throwing back data");
262
+ memcpy (buffer, output_buffer + bytes_written, len);
263
+ buffer [len] = 0;
264
+ OutboundPages.push_front (OutboundPage (buffer, len));
265
+ }
266
+ #ifdef HAVE_EPOLL
267
+ EpollEvent.events = (EPOLLIN | (SelectForWrite() ? EPOLLOUT : 0));
268
+ assert (MyEventMachine);
269
+ MyEventMachine->Modify (this);
270
+ #endif
271
+ }
272
+ else {
273
+ #ifdef OS_UNIX
274
+ if ((errno != EINPROGRESS) && (errno != EWOULDBLOCK) && (errno != EINTR))
275
+ #endif
276
+ #ifdef OS_WIN32
277
+ if ((errno != WSAEINPROGRESS) && (errno != WSAEWOULDBLOCK))
278
+ #endif
279
+ Close();
280
+ }
281
+ }
282
+
283
+
284
+ /*************************
285
+ PipeDescriptor::Heartbeat
286
+ *************************/
287
+
288
+ void PipeDescriptor::Heartbeat()
289
+ {
290
+ // If an inactivity timeout is defined, then check for it.
291
+ if (InactivityTimeout && ((gCurrentLoopTime - LastIo) >= InactivityTimeout))
292
+ ScheduleClose (false);
293
+ //bCloseNow = true;
294
+ }
295
+
296
+
297
+ /*****************************
298
+ PipeDescriptor::SelectForRead
299
+ *****************************/
300
+
301
+ bool PipeDescriptor::SelectForRead()
302
+ {
303
+ /* Pipe descriptors, being local by definition, don't have
304
+ * a pending state, so this is simpler than for the
305
+ * ConnectionDescriptor object.
306
+ */
307
+ return true;
308
+ }
309
+
310
+ /******************************
311
+ PipeDescriptor::SelectForWrite
312
+ ******************************/
313
+
314
+ bool PipeDescriptor::SelectForWrite()
315
+ {
316
+ /* Pipe descriptors, being local by definition, don't have
317
+ * a pending state, so this is simpler than for the
318
+ * ConnectionDescriptor object.
319
+ */
320
+ return (GetOutboundDataSize() > 0);
321
+ }
322
+
323
+
324
+
325
+
326
+ /********************************
327
+ PipeDescriptor::SendOutboundData
328
+ ********************************/
329
+
330
+ int PipeDescriptor::SendOutboundData (const char *data, int length)
331
+ {
332
+ //if (bCloseNow || bCloseAfterWriting)
333
+ if (IsCloseScheduled())
334
+ return 0;
335
+
336
+ if (!data && (length > 0))
337
+ throw std::runtime_error ("bad outbound data");
338
+ char *buffer = (char *) malloc (length + 1);
339
+ if (!buffer)
340
+ throw std::runtime_error ("no allocation for outbound data");
341
+ memcpy (buffer, data, length);
342
+ buffer [length] = 0;
343
+ OutboundPages.push_back (OutboundPage (buffer, length));
344
+ OutboundDataSize += length;
345
+ #ifdef HAVE_EPOLL
346
+ EpollEvent.events = (EPOLLIN | EPOLLOUT);
347
+ assert (MyEventMachine);
348
+ MyEventMachine->Modify (this);
349
+ #endif
350
+ return length;
351
+ }
352
+
353
+ /********************************
354
+ PipeDescriptor::GetSubprocessPid
355
+ ********************************/
356
+
357
+ bool PipeDescriptor::GetSubprocessPid (pid_t *pid)
358
+ {
359
+ bool ok = false;
360
+ if (pid && (SubprocessPid > 0)) {
361
+ *pid = SubprocessPid;
362
+ ok = true;
363
+ }
364
+ return ok;
365
+ }
366
+
367
+ #endif
368
+