├── .gitignore ├── .travis.yml ├── GNUmakefile ├── README.md ├── build.gradle ├── c_src └── gen_c_server.c ├── ebin └── gen_c_server.app ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── include └── gen_c_server.h ├── pthreads-win32 └── README.md ├── settings.gradle ├── src └── gen_c_server.erl └── test ├── call_counter ├── c_src │ └── call_counter.c └── call_counter_SUITE.erl ├── crashy ├── c_src │ └── crashy.c └── crashy_SUITE.erl └── reply_this ├── c_src └── reply_this.c └── reply_this_SUITE.erl /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | priv 3 | pthreads-win32 4 | .gradle 5 | 6 | *.o 7 | *~ 8 | *.beam 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | notifications: 3 | email: cesar.crusius@stanfordalumni.org 4 | otp_release: 5 | - 17.5 6 | script: 7 | - ./gradlew --info install 8 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | ## 2 | ## %CopyrightBegin% 3 | ## 4 | ## Copyright Cesar Crusius 2016. All Rights Reserved. 5 | ## 6 | ## Licensed under the Apache License, Version 2.0 (the "License"); 7 | ## you may not use this file except in compliance with the License. 8 | ## You may obtain a copy of the License at 9 | ## 10 | ## http://www.apache.org/licenses/LICENSE-2.0 11 | ## 12 | ## Unless required by applicable law or agreed to in writing, software 13 | ## distributed under the License is distributed on an "AS IS" BASIS, 14 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ## See the License for the specific language governing permissions and 16 | ## limitations under the License. 17 | ## 18 | ## %CopyrightEnd% 19 | ## 20 | all: install 21 | 22 | compile: ; ./gradlew build 23 | install: ; ./gradlew install 24 | clean: ; ./gradlew clean 25 | shell: ; epmd -daemon && ./gradlew shell && epmd -kill 26 | ct: ; epmd -daemon && ./gradlew ct && epmd -kill 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/ccrusius/gen_c_server.svg?branch=master)](https://travis-ci.org/ccrusius/gen_c_server) 2 | 3 | # Erlang Generic C Node Server 4 | 5 | 6 | **Table of Contents** 7 | 8 | - [Erlang Generic C Node Server](#erlang-generic-c-node-server) 9 | - [Summary](#summary) 10 | - [Erlang: The Behaviour](#erlang-the-behaviour) 11 | - [The `c_node/0` Callback](#the-cnode0-callback) 12 | - [The Internal Server State](#the-internal-server-state) 13 | - [Starting the Server, and the `init/2` Callback](#starting-the-server-and-the-init2-callback) 14 | - [Stopping the Server, and the `terminate/2` Callback](#stopping-the-server-and-the-terminate2-callback) 15 | - [Synchronous Calls to Erlang and the C Node](#synchronous-calls-to-erlang-and-the-c-node) 16 | - [Asynchronous Calls to Erlang and the C Node](#asynchronous-calls-to-erlang-and-the-c-node) 17 | - [Out-of-Band Messages](#out-of-band-messages) 18 | - [Callback Summary](#callback-summary) 19 | - [Function Summary](#function-summary) 20 | - [C: Writing a C Node](#c-writing-a-c-node) 21 | - [Limitations](#limitations) 22 | - [Compiling](#compiling) 23 | - [Compiling on Windows](#compiling-on-windows) 24 | 25 | 26 | 27 | A behaviour that ties together Erlang's `gen_server` with Erlang's C 28 | nodes. 29 | 30 | [C nodes](http://erlang.org/doc/tutorial/cnode.html) 31 | are a way of implementing native binaries, written in C, that behave 32 | like Erlang nodes, capable of sending and receiving messages from Erlang 33 | processes. Amongst the ways of interfacing Erlang with C, C nodes are the 34 | most robust: since the nodes are independent OS processes, crashes do not 35 | affect the Erlang VM. Compare this to a NIF, for example, where a problem in 36 | the C code may cause the entire Erlang VM to crash. The price to pay is, 37 | of course, performance. 38 | 39 | Making C nodes work with Erlang, however, is not a simple task. Your 40 | executable is started by hand, and has to set up communications with an 41 | existing Erlang process. Since the executable does not know where to connect 42 | to in advance, the necessary parameters have to be given to it in the 43 | command line when the C node is started. 44 | Once all of that is done, the C node 45 | enters a message loop, waiting for messages and replying to them. If the 46 | computations it is performing take too long, the calling Erlang process may 47 | infer that the C node is dead, and cut communications with it. This is 48 | because Erlang will be sending `TICK` messages to the C node to keep the 49 | connection alive. The only way around this is to make the C node have 50 | separate threads to deal with user-generated messages and system-generated 51 | messages. The list does go on. 52 | 53 | This module, and its accompanying library, `libgen_c_server.a`, hide this 54 | complexity and require the C node implementer to define only the equivalent 55 | `gen_server` callbacks in C, and a `gen_c_server` callback module 56 | in Erlang. Within the C callbacks, the developer uses 57 | the `ei` library to manipulate Erlang terms. 58 | 59 | Inspired by Robby Raschke's work on 60 | [interfacing with Lua](https://github.com/rtraschke/erlang-lua). 61 | 62 | # Summary 63 | 64 | 1. [Write a C node](#c-writing-a-c-node) 65 | 2. Write an Erlang callback module with `gen_c_server` behaviour. 66 | * Call `c_init` from `init`, and `c_terminate` from `terminate`. 67 | * Forward `handle_info` to `c_handle_info` if desired. 68 | 69 | We will start with the Erlang callback module description, since this 70 | part should be more familiar to Erlang users. 71 | 72 | # Erlang: The Behaviour 73 | 74 | If you are not familiar with the 75 | standard [`gen_server`](http://erlang.org/doc/man/gen_server.html) 76 | behaviour, please take some time to study it before tackling this 77 | one. The description that follows relies heavily on describing 78 | `gen_c_server` _in terms of `gen_server`_. 79 | 80 | We will describe how to write the C node 81 | in [another section](#c-writing-a-c-node). In this one, we'll describe 82 | the behaviour functions and callbacks. In what follows, `Mod` will 83 | refer to the Erlang module implementing this behaviour. That module's 84 | definition will contain something like this at the beginning: 85 | ```erlang 86 | -module(example_module). 87 | -behaviour(gen_c_server). 88 | ``` 89 | 90 | ## The `c_node/0` Callback 91 | 92 | The callback module should define a `c_node` function, which returns a 93 | string with the path to the C node executable. 94 | ```erlang 95 | c_node() -> 96 | "/path/to/c/node/executable". 97 | ``` 98 | 99 | ## The Internal Server State 100 | 101 | In `gen_server`, the internals of the server implementation are 102 | completely hidden. In `gen_c_server`, however, there is one bit of 103 | internal server state that gets passed around in some functions. The 104 | internal server state is a tuple `{State, Opaque}`, where `State` is 105 | the equivalent to `gen_server`'s state, and `Opaque` is something 106 | internal to the `gen_c_server`. This tuple is passed in to every 107 | callback that normally takes in a state argument. Those callbacks 108 | should also return a `{NewState, NewOpaque}` instead of the simple 109 | state value. 110 | 111 | The reason this has to be exposed is because, in callbacks such as 112 | `init/2` and `terminate/2`, the callback has to ask the server to talk 113 | to the C node, and the `Opaque` structure gives `gen_c_server` the 114 | information needed to do so. This will become clearer when we document 115 | the callbacks below, but now you know the reason why `Opaque` will pop 116 | up here and there. 117 | 118 | ## Starting the Server, and the `init/2` Callback 119 | 120 | In a `gen_server`, the callback module needs to implement a `init/1` 121 | callback, which is called by the various server `start` functions. In 122 | `gen_c_server`, this function takes an extra argument, `Opaque`, 123 | representing the internal state of the server. 124 | 125 | It is the job of this `init/2` callback to initialize the C node, and 126 | it can do that by a call to `gen_c_server:c_init(Args, Opaque)`, where 127 | `Args` is an arbitrary value (usually the same as the first input 128 | argument), and `Opaque` is the second input value to `init/2`. The 129 | function will start the C node and call its `gcs_init` 130 | function. Depending on what it returns, `c_init` will return one of 131 | 132 | * `{ok, {State, NewOpaque}}` 133 | * `{ok, {State, NewOpaque}, hibernate | Timeout}` 134 | * `{stop, Reason}` 135 | * `ignore` 136 | 137 | The meaning of those is similar to the usual `gen_server` expected 138 | returns from `init/1`, except they need to include the `Opaque` 139 | parameters in the state. 140 | 141 | The `init/2` callback should return one of those values too, so its 142 | simplest implementation, if the Erlang portion does not care about 143 | state or massaging the `Args` parameters, is 144 | ```erlang 145 | init(Args, Opaque) -> gen_c_server:c_init(Args, Opaque). 146 | ``` 147 | Of course, the function is free to modify the input `Args`, or the 148 | `State` returned by `c_init`. What it should _not_ do is mess 149 | around with the `Opaque` parameters. 150 | 151 | Lastly, there is a `c_init/3` function that takes as its third 152 | argument the "spawn type" for the C node. The possible values are 153 | `spawn_executable` and `spawn`. The first is the value `c_init/2` 154 | uses. The second is useful when the C node executable is wrapped in a 155 | shell script. 156 | 157 | ## Stopping the Server, and the `terminate/2` Callback 158 | 159 | The logic behind the `init/2` callback applies verbatim to the 160 | `terminate/2` callback. In `gen_server`, the callback takes in the 161 | `Reason` and the `State`, and does whatever it needs to do to clean 162 | up. In `gen_c_server`, there are two differences: 163 | 164 | * The `terminate` state argument is instead `{State, Opaque}`, for 165 | reasons described above. 166 | * The callback should call the `gen_c_server:c_terminate` function, 167 | which will take care of calling the C node's `gcs_terminate` 168 | function, and shutting the C node down. 169 | 170 | The `c_terminate` function accepts the same parameters as 171 | `terminate/2`, so the simplest callback implementation is 172 | ```erlang 173 | terminate(Reason, {State, Opaque} = ServerState) -> 174 | gen_c_server:c_terminate(Reason, ServerState). 175 | ``` 176 | 177 | ## Synchronous Calls to Erlang and the C Node 178 | 179 | Synchronous calls to the Erlang callback module are done by calling 180 | `gen_c_server:call/2`. The Erlang callback `handle_call/3` is called, 181 | and again it takes in a `{State, Opaque}` tuple instead of 182 | `gen_server`'s single `State`. When it returns new state, it must 183 | return it in the form `{NewState, Opaque}`. 184 | 185 | The `Opaque` value would only be useful if the `handle_call/3` 186 | callback could call the C node somehow. Currently that is not 187 | possible, but by having the parameter there we can add the 188 | functionality without breaking backwards compatibility. 189 | 190 | Synchronous calls to the C node are made via the 191 | `gen_c_server:c_call/2` function, which calls the `gcs_handle_call` C 192 | node callback. This callback does not receive the `{State, Opaque}` 193 | tuple, since it doesn't need `Opaque` for anything. Instead, it simply 194 | receives the `State`. 195 | 196 | ## Asynchronous Calls to Erlang and the C Node 197 | 198 | Asynchronous calls follow the same logic as synchronous ones, except 199 | that `call` is replaced by `cast`: The `gen_c_server:cast/2` function 200 | calls the `handle_cast` callback which gets `{State, Opaque}` and 201 | needs to reply with `{NewState, Opaque}`. Asynchronous calls to the C 202 | node are done with `gen_c_server:c_cast/2`, and call the C node's 203 | `gcs_handle_call` callback with `State`. 204 | 205 | ## Out-of-Band Messages 206 | 207 | Lastly, out-of-band messages are always passed on to the Erlang 208 | module's `handle_info/2` callback, which, as usual, gets the 209 | `{State, Opaque}` tuple as the state argument. The callback can pass 210 | the message on to the C node by calling `gen_c_server:c_handle_info/2` 211 | with the same arguments as its input, which will result in a call to 212 | the C node's `gcs_handle_info`. 213 | 214 | ## Callback Summary 215 | 216 | * `init/2` 217 | * `terminate/2` 218 | * `c_node/0` 219 | * `handle_call/3` 220 | * `handle_cast/2` 221 | * `handle_info/2` 222 | 223 | ## Function Summary 224 | 225 | * `start/3`, `start/4`, `start_link/3`, `start_link/4`: These are 226 | completely equivalent to `gen_server`'s functions of the same 227 | signature. 228 | * `stop/1`: Terminates the server, including shutting down the C node. 229 | * `call/2`, `call/3` 230 | * `cast/2` 231 | * `c_init/2` 232 | * `c_call/2`, `c_call/3` 233 | * `c_terminate/2` 234 | * `c_cast/2` 235 | * `c_handle_info/2` 236 | 237 | 238 | # C: Writing a C Node 239 | 240 | A C node is written by implementing the necessary callbacks, and linking 241 | the executable against `libgen_c_server.a`, which defines `main()` for you. 242 | When implementing callbacks, the rule of thumb is this: for each callback 243 | you would implement in an Erlang `gen_server` callback module, implement a 244 | `gcs_`_name_ function in C instead. This function takes in the same 245 | arguments as the Erlang callback, plus a last `ei_x_buff*` argument where the 246 | function should build its reply (when a reply is needed). The other 247 | arguments are `const char*` pointing to `ei` buffers containing the 248 | arguments themselves. C node callbacks that receive a state receive 249 | `State`, not the `{State, Opaque}` tuple the Erlang callbacks 250 | receive. (The `Opaque` argument is necessary in Erlang so the C node 251 | can be called, so it is useless inside the C node itself.) 252 | 253 | A full C node skeleton thus looks like this: 254 | ```c 255 | #include "gen_c_server.h" 256 | 257 | void gcs_init(const char *args_buff, ei_x_buff *reply) { 258 | /* IMPLEMENT ME */ 259 | } 260 | 261 | void gcs_handle_call(const char *request_buff, 262 | const char *from_buff, 263 | const char *state_buff, 264 | ei_x_buff *reply) { 265 | /* IMPLEMENT ME */ 266 | } 267 | 268 | void gcs_handle_cast(const char *request_buff, 269 | const char *state_buff, 270 | ei_x_buff *reply) { 271 | /* IMPLEMENT ME */ 272 | } 273 | 274 | void gcs_handle_info(const char *info_buff, 275 | const char *state_buff, 276 | ei_x_buff *reply) { 277 | /* IMPLEMENT ME */ 278 | } 279 | 280 | void gcs_terminate(const char *reason_buff, const char *state_buff) { 281 | /* IMPLEMENT ME */ 282 | } 283 | ``` 284 | Let's see how this looks like in a C node that simply counts the number 285 | of calls made to each function. (This C node is included in the source 286 | distribution of `gen_c_server`, as one of the tests.) In this C node, 287 | We make use of an utility `gcs_decode` function which is provided by the 288 | `gen_c_server` library, that allows us to decode `ei` buffers in a 289 | `sscanf`-like manner. Consult the header file for more details. 290 | ```c 291 | #include "gen_c_server.h" 292 | 293 | /* 294 | * Utility function that encodes a state tuple based on the arguments. 295 | * The state is a 3-tuple, { num_calls, num_casts, num_infos } 296 | */ 297 | static void encode_state( 298 | ei_x_buff *reply, 299 | long ncalls, 300 | long ncasts, 301 | long ninfos) 302 | { 303 | ei_x_encode_tuple_header(reply,3); 304 | ei_x_encode_long(reply,ncalls); 305 | ei_x_encode_long(reply,ncasts); 306 | ei_x_encode_long(reply,ninfos); 307 | } 308 | 309 | /* 310 | * Initialize the C node, replying with a zeroed-out state. 311 | */ 312 | void gcs_init(const char* args_buff, ei_x_buff *reply) 313 | { 314 | /* Reply: {ok,{0,0,0}} */ 315 | ei_x_encode_tuple_header(reply,2); 316 | ei_x_encode_atom(reply,"ok"); 317 | encode_state(reply,0,0,0); 318 | } 319 | 320 | /* 321 | * When called, increment the number of calls, and reply with the new state. 322 | */ 323 | void gcs_handle_call( 324 | const char *request_buff, 325 | const char *from_buff, 326 | const char *state_buff, 327 | ei_x_buff *reply) 328 | { 329 | long ncalls, ncasts, ninfos; 330 | gcs_decode(state_buff,(int*)0,"{lll}",3,&ncalls,&ncasts,&ninfos); 331 | 332 | /* Reply: {reply,Reply=NewState,NewState={ncalls+1,ncasts,ninfos}} */ 333 | ei_x_encode_tuple_header(reply,3); 334 | ei_x_encode_atom(reply,"reply"); 335 | encode_state(reply,ncalls+1,ncasts,ninfos); 336 | encode_state(reply,ncalls+1,ncasts,ninfos); 337 | } 338 | 339 | /* 340 | * When casted, increment the number of casts, and reply with the new state. 341 | */ 342 | void gcs_handle_cast( 343 | const char *request_buff, 344 | const char *state_buff, 345 | ei_x_buff *reply) 346 | { 347 | long ncalls, ncasts, ninfos; 348 | gcs_decode(state_buff,(int*)0,"{lll}",3,&ncalls,&ncasts,&ninfos); 349 | 350 | /* Reply: {noreply,NewState={ncalls,ncasts+1,ninfos}} */ 351 | ei_x_encode_tuple_header(reply,2); 352 | ei_x_encode_atom(reply,"noreply"); 353 | encode_state(reply,ncalls,ncasts+1,ninfos); 354 | } 355 | 356 | /* 357 | * When info-ed, increment the number of infos, and reply with the new state. 358 | */ 359 | void gcs_handle_info( 360 | const char *info_buff, 361 | const char *state_buff, 362 | ei_x_buff *reply) 363 | { 364 | long ncalls, ncasts, ninfos; 365 | gcs_decode(state_buff,(int*)0,"{lll}",3,&ncalls,&ncasts,&ninfos); 366 | 367 | /* Reply: {noreply,NewState={ncalls,ncasts,ninfos+1}} */ 368 | ei_x_encode_tuple_header(reply,2); 369 | ei_x_encode_atom(reply,"noreply"); 370 | encode_state(reply,ncalls,ncasts,ninfos+1); 371 | } 372 | 373 | /* 374 | * We don't need to clean anything up when terminating. 375 | */ 376 | void gcs_terminate(const char *reason_buff, const char *state_buff) { } 377 | ``` 378 | 379 | Once you compile (with the appropriate flags so the compiler finds 380 | `gen_c_server.h` and `ei.h`) and link (with the appropriate flags to the 381 | linker links in `libgen_c_server`, `erl_interface`, and `ei` libraries) this 382 | node, you can define a simple Erlang callback module as follows: 383 | ```erlang 384 | -module(my_c_server). 385 | -behaviour(gen_c_server). 386 | -export([c_node/0, init/2, terminate/2, handle_info/2]). 387 | 388 | c_node() -> "/path/to/my/c/node/executable". 389 | init(Args, Opaque) -> gen_c_server:c_init(Args, Opaque). 390 | terminate(Reason, ServerState) -> gen_c_server:c_terminate(Reason, ServerState). 391 | handle_info(Info, ServerState) -> gen_c_server:c_handle_info(Info, ServerState). 392 | ``` 393 | With that, you can now: 394 | ```erlang 395 | {ok,Pid} = gen_c_server:start(my_c_server,[],[]), 396 | {1,0,0} = gen_c_server:c_call(Pid,"Any message"), 397 | ok = gen_c_server:c_cast(Pid,"Any old message"), 398 | {2,1,0} = gen_c_server:c_call(Pid,"Any message"), 399 | Pid ! "Any message, really", 400 | {3,1,1} = gen_c_server:c_call(Pid,[]), 401 | gen_c_server:stop(Pid). 402 | ``` 403 | Note that the Erlang shell has to have a registered name, which means 404 | you have to start it with either the `-name` or `-sname` options passed in. 405 | This is because the C node needs a registered Erlang node to connect to. 406 | 407 | ## Limitations 408 | 409 | Currently, not all replies from the callback functions are accepted. 410 | In particular, 411 | 412 | * `gcs_handle_call` can only reply `{reply,Reply,NewState}`, 413 | `{reply,Reply,NewState,hibernate}` or `{stop,Reason,Reply,NewState}`. 414 | * `gcs_handle_cast` can only reply `{noreply,NewState}`, 415 | `{noreply,NewState,hibernate}` or `{stop,Reason,NewState}`. 416 | * `gcs_handle_info` can only reply `{noreply,NewState}`, 417 | `{noreply,NewState,hibernate}` or `{stop,Reason,NewState}`. 418 | 419 | ## Compiling 420 | 421 | In a Unix system, typing `./gradlew install` should do it. Type 422 | `./gradlew tasks` to get a list of available build targets. 423 | 424 | A makefile is provided with convenience targets used during 425 | development. The `ct` target, for example, sets up and tears down 426 | `epmd` automatically so that tests can be performed. `make shell` will 427 | bring you into an Erlang shell with the correct paths set for 428 | interactively testing things. 429 | 430 | ## Compiling on Windows 431 | 432 | Download the latest 433 | [pthreads-win32](https://sourceware.org/pthreads-win32/) pre-compiled 434 | ZIP. Unzip it into a `pthreads-win32` directory at the root of your 435 | checkout of this project. This should allow you to build with the 436 | Gradle script. 437 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.zoltu.git-versioning" version "2.0.28" 3 | id "org.ccrusius.erlang" version "1.3.7" 4 | } 5 | 6 | apply plugin: 'c' 7 | 8 | //============================================================================= 9 | // 10 | // Why not rebar? 11 | // 12 | // Because, while it works well in its own space, as soon as you get out of it 13 | // it is not flexible enough. The C compilation support, for example, is not 14 | // really there. I need something that works on SmartOS (Solaris), Windows, Mac, 15 | // and Linux. My options are Gradle, CMake, and Shake, and Gradle is the one 16 | // that made all the integrations I needed the least painful. (Bazel 17 | // is currently not an option because of its lack of Windows support.) 18 | // 19 | //============================================================================= 20 | 21 | /// --------------------------------------------------------------------------- 22 | /// 23 | /// Gradle wrapper configuration 24 | /// 25 | /// To upgrade the wrapper, change the version here and run `./gradlew wrapper` 26 | /// 27 | /// --------------------------------------------------------------------------- 28 | task wrapper(type: Wrapper) { 29 | gradleVersion = '3.0' 30 | } 31 | 32 | //============================================================================= 33 | // 34 | // Empty tasks to be extended as we go. 35 | // 36 | //============================================================================= 37 | task(install) 38 | 39 | /// --------------------------------------------------------------------------- 40 | /// 41 | /// Erlang 42 | /// 43 | /// --------------------------------------------------------------------------- 44 | ext { 45 | eiInclude = erlang.eval('io:format("~s", [code:lib_dir(erl_interface, include)]).') 46 | eiLib = erlang.eval('io:format("~s", [code:lib_dir(erl_interface, lib)]).') 47 | } 48 | 49 | install.dependsOn ebuild 50 | 51 | //============================================================================= 52 | // 53 | // Sources 54 | // 55 | //============================================================================= 56 | task install_src(type: Copy) { 57 | from 'src' 58 | into "$ebuildAppDir/src" 59 | include '**/*.erl' 60 | } 61 | 62 | install.dependsOn install_src 63 | 64 | //============================================================================= 65 | // 66 | // Includes 67 | // 68 | //============================================================================= 69 | task install_include(type: Copy) { 70 | from 'include' 71 | into "$ebuildAppDir/include" 72 | include '**/*.h' 73 | } 74 | 75 | install.dependsOn install_include 76 | 77 | /// --------------------------------------------------------------------------- 78 | /// 79 | /// Testing 80 | /// 81 | /// --------------------------------------------------------------------------- 82 | /// 83 | /// One thing one may want to be able to do is to easily change the 84 | /// name of the behavior without having to edit the file itself (so 85 | /// the name change can be automated easily in build scripts). 86 | /// 87 | 88 | task custom_gen_c_server(type: org.ccrusius.erlang.tasks.Compile) { 89 | setSourceFile 'src/gen_c_server.erl' 90 | setNewName 'custom_gen_c_server.erl' 91 | addReplacement '\\bgen_c_server\\b', 'custom_gen_c_server' 92 | setOutputDir "$rootDir/test/custom_gen_c_server-1.0/ebin" 93 | } 94 | 95 | task ct { 96 | dependsOn install 97 | ext.destDir = new File("$rootDir/build/test") 98 | ext.suites = fileTree(dir: "$rootDir/test", include: '**/*_SUITE.erl') 99 | doLast { 100 | destDir.mkdirs() 101 | exec { 102 | executable 'ct_run' 103 | args '-pa', "$ebuildAppDir/ebin" 104 | args '-pa', "$rootDir/test/custom_gen_c_server-1.0/ebin" 105 | args '-logdir', "$destDir" 106 | args '-suite' 107 | args suites 108 | standardInput System.in 109 | environment 'ROOT_DIR': "$rootDir" 110 | } 111 | } 112 | } 113 | 114 | ct.dependsOn custom_gen_c_server 115 | 116 | //============================================================================= 117 | // 118 | // Erlang shell 119 | // 120 | //============================================================================= 121 | task shell { 122 | dependsOn install 123 | doLast { 124 | def command = [ 'erl', '-name', 'gradle', '-pa', "$ebuildAppDir/ebin" ] 125 | def proc = new ProcessBuilder((String[])command) 126 | .redirectOutput(ProcessBuilder.Redirect.INHERIT) 127 | .redirectInput(ProcessBuilder.Redirect.INHERIT) 128 | .redirectError(ProcessBuilder.Redirect.INHERIT) 129 | .start() 130 | proc.waitFor() 131 | if (0 != proc.exitValue()) { 132 | throw new RuntimeException( 133 | "erlang shell exited with status: ${proc.exitValue()}") 134 | } 135 | } 136 | } 137 | 138 | //============================================================================= 139 | // 140 | // C 141 | // 142 | //============================================================================= 143 | model { 144 | 145 | platforms { 146 | x64 { architecture 'x86_64' } 147 | } 148 | 149 | repositories { 150 | libs(PrebuiltLibraries) { 151 | ei { 152 | headers.srcDir "$eiInclude" 153 | binaries.withType(StaticLibraryBinary) { binary -> 154 | if (binary.targetPlatform.operatingSystem.windows) { 155 | staticLibraryFile = file("$eiLib\\ei.lib") 156 | } 157 | if (binary.targetPlatform.operatingSystem.linux || 158 | binary.targetPlatform.operatingSystem.macOsX) { 159 | staticLibraryFile = file("$eiLib/libei.a") 160 | } 161 | } 162 | } 163 | erl_interface { 164 | headers.srcDir "$eiInclude" 165 | binaries.withType(StaticLibraryBinary) { binary -> 166 | if (binary.targetPlatform.operatingSystem.windows) { 167 | staticLibraryFile = file("$eiLib\\erl_interface.lib") 168 | } 169 | if (binary.targetPlatform.operatingSystem.linux || 170 | binary.targetPlatform.operatingSystem.macOsX) { 171 | staticLibraryFile = file("$eiLib/liberl_interface.a") 172 | } 173 | } 174 | } 175 | pthread { 176 | binaries.withType(SharedLibraryBinary) { binary -> 177 | def p = binary.targetPlatform 178 | if (p.operatingSystem.windows) { 179 | def baseDir = "$rootDir\\pthreads-win32\\Pre-built.2" 180 | headers.srcDir "$baseDir\\include" 181 | sharedLibraryFile = file("$baseDir\\dll\\$p.name\\pthreadVC2.dll") 182 | sharedLibraryLinkFile = file("$baseDir\\lib\\$p.name\\pthreadVC2.lib") 183 | } 184 | } 185 | } 186 | winsock { 187 | binaries.withType(StaticLibraryBinary) { binary -> 188 | def p = binary.targetPlatform 189 | if (p.operatingSystem.windows) { 190 | // TODO: Find ws2_32 instead of hardcoding the path 191 | def baseDir = "C:\\Program Files (x86)\\Windows Kits\\8.0\\Lib\\win8\\um" 192 | staticLibraryFile = file("$baseDir\\$p.name\\ws2_32.lib") 193 | } 194 | } 195 | } 196 | } 197 | } 198 | 199 | binaries { 200 | all { 201 | cCompiler.define "_REENTRANT" 202 | 203 | lib library: 'erl_interface', linkage: 'static' 204 | lib library: 'ei', linkage: 'static' 205 | 206 | if (toolChain in VisualCpp) { 207 | // MSVC defines '_WIN32', but Erlang includes rely on '__WIN32__' 208 | cCompiler.define "__WIN32__" 209 | cCompiler.args "/TP" 210 | linker.args "/SUBSYSTEM:CONSOLE" 211 | lib library: 'winsock', linkage: 'static' 212 | lib library: 'pthread', linkage: 'shared' 213 | } 214 | 215 | if (targetPlatform.operatingSystem.linux) { 216 | cCompiler.args '-pthread' 217 | linker.args '-pthread' 218 | } 219 | 220 | //println 'configuring variant: ' + name + '.' + targetPlatform.name + '.' + buildType.name 221 | } 222 | } 223 | 224 | components { 225 | //========================================================================= 226 | // 227 | // gen_c_server native library 228 | // 229 | //========================================================================= 230 | gen_c_server(NativeLibrarySpec) { 231 | targetPlatform 'x64' 232 | binaries.withType(SharedLibraryBinarySpec) { 233 | buildable = false 234 | } 235 | sources.c { 236 | source { 237 | srcDir "c_src" 238 | include "*.c" 239 | } 240 | exportedHeaders { 241 | srcDir "include" 242 | } 243 | } 244 | binaries.withType(StaticLibraryBinarySpec) { lib -> 245 | lib.tasks.withType(CreateStaticLibrary) { linkTask -> 246 | linkTask << { 247 | copy { 248 | from lib.staticLibraryFile 249 | into file("$ebuildAppDir/lib/") 250 | } 251 | } 252 | install.dependsOn lib 253 | } 254 | } 255 | } 256 | //========================================================================= 257 | // 258 | // Test C nodes 259 | // 260 | //========================================================================= 261 | call_counter(NativeExecutableSpec) { 262 | targetPlatform 'x64' 263 | sources.c { 264 | source { 265 | srcDir "test/call_counter/c_src" 266 | include "*.c" 267 | } 268 | exportedHeaders { 269 | srcDir "include" 270 | } 271 | } 272 | binaries.all { 273 | lib library: 'gen_c_server', linkage: 'static' 274 | } 275 | } 276 | crashy(NativeExecutableSpec) { 277 | targetPlatform 'x64' 278 | sources.c { 279 | source { 280 | srcDir "test/crashy/c_src" 281 | include "*.c" 282 | } 283 | exportedHeaders { 284 | srcDir "include" 285 | } 286 | } 287 | binaries.all { 288 | lib library: 'gen_c_server', linkage: 'static' 289 | } 290 | } 291 | reply_this(NativeExecutableSpec) { 292 | targetPlatform 'x64' 293 | sources.c { 294 | source { 295 | srcDir "test/reply_this/c_src" 296 | include "*.c" 297 | } 298 | exportedHeaders { 299 | srcDir "include" 300 | } 301 | } 302 | binaries.all { 303 | lib library: 'gen_c_server', linkage: 'static' 304 | } 305 | } 306 | } 307 | } 308 | 309 | // 310 | // For each executable, gradle defines a task called 311 | // install${binary.name}Executable, which will create a suitable 312 | // installation that includes all the library dependencies. 313 | // This installation will have a shell script placed at 314 | // ${project.buildDir}/install/${binary.name}/${binary.name} 315 | // (with a .bat extension on Windows). 316 | // 317 | install.dependsOn 'installCall_counterExecutable' 318 | install.dependsOn 'installCrashyExecutable' 319 | install.dependsOn 'installReply_thisExecutable' 320 | -------------------------------------------------------------------------------- /c_src/gen_c_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | %CopyrightBegin% 3 | 4 | Copyright Cesar Crusius 2016. All Rights Reserved. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | %CopyrightEnd% 19 | */ 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "gen_c_server.h" 27 | 28 | #include "erl_interface.h" 29 | 30 | #define GCS_EXIT_INVALID_ARGC 1 31 | #define GCS_EXIT_GEN_CALL_SEND 2 32 | #define GCS_EXIT_NO_HOST 3 33 | #define GCS_EXIT_EI_CONNECT_INIT 4 34 | #define GCS_EXIT_EI_CONNECT 5 35 | #define GCS_EXIT_PTHREAD_CREATE 6 36 | #define GCS_EXIT_EI_RECONNECT 7 37 | #define GCS_EXIT_EI_SEND 8 38 | #define GCS_EXIT_GEN_CALL 9 39 | #define GCS_EXIT_MUTEX_LOCK 10 40 | 41 | /*============================================================================== 42 | * 43 | * ei utilities 44 | * 45 | *============================================================================*/ 46 | static void 47 | erlang_pid_dup(const erlang_pid *in, erlang_pid *out) 48 | { *out = *in; memcpy(out->node,in->node,MAXATOMLEN_UTF8); } 49 | 50 | static void 51 | erlang_trace_dup(const erlang_trace *in, erlang_trace *out) 52 | { *out = *in; erlang_pid_dup(&in->from,&out->from); } 53 | 54 | static void 55 | erlang_msg_dup(const erlang_msg *in, erlang_msg *out) 56 | { 57 | *out = *in; 58 | erlang_pid_dup(&in->from,&out->from); 59 | erlang_pid_dup(&in->to,&out->to); 60 | memcpy(out->toname,in->toname,MAXATOMLEN_UTF8); 61 | memcpy(out->cookie,in->cookie,MAXATOMLEN_UTF8); 62 | erlang_trace_dup(&in->token,&out->token); 63 | } 64 | 65 | static void 66 | ei_x_buff_dup(const ei_x_buff *in, ei_x_buff *out) 67 | { 68 | if (out->buff) free(out->buff); 69 | *out = *in; 70 | if (out->buffsz == 0 && out->buff == NULL) return; 71 | out->buff = (char*) malloc(in->buffsz); 72 | memcpy(out->buff,in->buff,out->buffsz); 73 | } 74 | 75 | int gcs_decode(const char* buffer, int *index, const char* fmt, ...) 76 | { 77 | va_list args; 78 | va_start(args,fmt); 79 | 80 | const char *ptr = fmt; 81 | 82 | int aux_index = 0; 83 | if(!index) index = &aux_index; 84 | 85 | const int original_index = *index; 86 | 87 | union { 88 | int aux_int; 89 | long aux_long; 90 | erlang_pid aux_pid; 91 | erlang_ref aux_ref; 92 | char aux_atom[MAXATOMLEN_UTF8]; 93 | } aux_value; 94 | 95 | for(;*ptr;++ptr) { 96 | if (*ptr == '{') { 97 | int desired = va_arg(args,int); 98 | if (ei_decode_tuple_header(buffer,index,&aux_value.aux_int)) break; 99 | if (desired > 0 && aux_value.aux_int != desired) break; 100 | } 101 | else if (*ptr == '}') { 102 | /* Ignore */ 103 | } 104 | else if (*ptr == '_') { 105 | if (ei_skip_term(buffer,index)) break; 106 | } 107 | else if (*ptr == 'a') { 108 | char* atom = va_arg(args,char*); 109 | if (!atom) atom = aux_value.aux_atom; 110 | if (ei_decode_atom(buffer,index,atom)) break; 111 | } 112 | else if (*ptr == 'A') { 113 | const char *desired = va_arg(args,const char*); 114 | if (ei_decode_atom(buffer,index,aux_value.aux_atom)) break; 115 | if (desired && strcmp(desired,aux_value.aux_atom) != 0) break; 116 | } 117 | else if (*ptr == 'l') { 118 | long* val = va_arg(args,long*); 119 | if (!val) val = &aux_value.aux_long; 120 | if (ei_decode_long(buffer,index,val)) break; 121 | } 122 | else if (*ptr == 'p') { 123 | erlang_pid* pid = va_arg(args,erlang_pid*); 124 | if (!pid) pid = &aux_value.aux_pid; 125 | if (ei_decode_pid(buffer,index,pid)) break; 126 | } 127 | else if (*ptr == 'r') { 128 | erlang_ref* ref = va_arg(args,erlang_ref*); 129 | if (!ref) ref = &aux_value.aux_ref; 130 | if (ei_decode_ref(buffer,index,ref)) break; 131 | } 132 | else if (*ptr == 't') { 133 | const char** term = va_arg(args,const char**); 134 | if (term) *term = buffer + *index; 135 | if (ei_skip_term(buffer,index)) break; 136 | } 137 | else if (*ptr == 'v') { 138 | int* version = va_arg(args,int*); 139 | if (!version) version = &aux_value.aux_int; 140 | if (ei_decode_version(buffer,index,version)) break; 141 | } 142 | else break; 143 | } 144 | 145 | va_end(args); 146 | 147 | if (*ptr) *index = original_index; 148 | 149 | return ptr-fmt; 150 | } 151 | 152 | /*============================================================================== 153 | * 154 | * The internal C node state. 155 | * 156 | * The state holds information about the node identification, and the current 157 | * connection to the Erlang node. 158 | * 159 | *============================================================================*/ 160 | struct c_node_state { 161 | char *c_node_name; /* C node 'alive name' */ 162 | char *c_node_hostname; /* FQDN host name */ 163 | char *c_node_fullname; /* Convenience: node_name@node_hostname */ 164 | 165 | char *erlang_node_fullname; /* Erlang node we're connected to. */ 166 | char *erlang_node_cookie; /* Cookie used for communications. */ 167 | 168 | ei_cnode ec; /* The EI connection structure. */ 169 | int connection_fd; /* The connection file descriptor. */ 170 | }; 171 | 172 | /*============================================================================== 173 | * 174 | * Printing 175 | * 176 | * Our convention is that a specific line, defined by C_NODE_END_MSG, is 177 | * printed at the end of every message sent. This allows the Erlang node 178 | * to know when we're done sending what we want to send. 179 | * 180 | * Since printing functions may be called from multiple threads, we put 181 | * a mutex around them so that messages don't get chopped up. 182 | * 183 | *============================================================================*/ 184 | static const char *C_NODE_START_MSG = "... - .- .-. -"; /* Morse code for "start" */ 185 | static const char *C_NODE_END_MSG = ". -. -.."; /* Morse code for "end" */ 186 | 187 | static pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER; 188 | 189 | static void _vprint(const char* prefix, const char* fmt, va_list args) 190 | { 191 | pthread_mutex_lock(&print_mutex); 192 | printf("\n%s\n",C_NODE_START_MSG); 193 | printf("%s",prefix); 194 | vprintf(fmt,args); 195 | printf("\n%s\n",C_NODE_END_MSG); 196 | fflush(stdout); 197 | pthread_mutex_unlock(&print_mutex); 198 | } 199 | 200 | #define C_NODE_PRINT(prefix) \ 201 | va_list args; va_start(args,fmt); _vprint(prefix,fmt,args); va_end(args); 202 | 203 | static void info_print(const char *fmt,...) { 204 | if (ei_get_tracelevel() > 0) { C_NODE_PRINT(" "); } 205 | } 206 | //static void warning_print(const char *fmt,...) { C_NODE_PRINT(""); } 207 | static void error_print(const char *fmt,...) { C_NODE_PRINT(""); } 208 | 209 | static void debug_print(const char *fmt,...) { 210 | if (ei_get_tracelevel() > 2) { C_NODE_PRINT("[DEBUG] "); } 211 | } 212 | 213 | static void trace_print(const char *fmt,...) { 214 | if (ei_get_tracelevel() > 3) { C_NODE_PRINT("[TRACE] "); } 215 | } 216 | 217 | /* ========================================================================= 218 | * 219 | * Pthread utilities 220 | * 221 | * ========================================================================= */ 222 | static int pthread_failures = 0; 223 | static const int PTHREAD_MAX_FAILURES = 1; 224 | 225 | static void increment_pthread_failures(const char* msg, int status) 226 | { 227 | ++pthread_failures; 228 | error_print(msg); 229 | if(pthread_failures < PTHREAD_MAX_FAILURES) return; 230 | error_print("Too many pthread failures"); 231 | exit(status); 232 | } 233 | 234 | #define gcs_pthread_mutex_lock(VAR) \ 235 | while(pthread_mutex_lock(&VAR) != 0) \ 236 | increment_pthread_failures(\ 237 | "pthread_mutex_lock failed (" #VAR ")",\ 238 | GCS_EXIT_MUTEX_LOCK); 239 | 240 | /*============================================================================== 241 | * 242 | * Main 243 | * 244 | * Establish a connection to the Erlang node, and enter the main message loop. 245 | * 246 | *============================================================================*/ 247 | static void _connect(struct c_node_state *state); 248 | static void _main_message_loop(struct c_node_state *state); 249 | 250 | int main(int argc, char *argv[]) 251 | { 252 | setvbuf(stdout, NULL, _IONBF, 0); 253 | setvbuf(stderr, NULL, _IONBF, 0); 254 | 255 | if(argc != 6) { 256 | error_print( 257 | "Invalid arguments when starting generic C node.\n" 258 | "Is this executable being called from gen_c_server?\n" 259 | "Usage: %s \n" 260 | "Call: %s %s %s %s %s %s %s %s %s %s", 261 | argv[0], argv[0], 262 | (argc > 1 ? argv[1] : ""), 263 | (argc > 2 ? argv[2] : ""), 264 | (argc > 3 ? argv[3] : ""), 265 | (argc > 4 ? argv[4] : ""), 266 | (argc > 5 ? argv[5] : ""), 267 | (argc > 6 ? argv[6] : ""), 268 | (argc > 7 ? argv[7] : ""), 269 | (argc > 8 ? argv[8] : ""), 270 | (argc > 9 ? argv[9] : "")); 271 | exit(GCS_EXIT_INVALID_ARGC); 272 | } 273 | 274 | erl_init(NULL,0); 275 | 276 | struct c_node_state *state = (struct c_node_state*)malloc(sizeof *state); 277 | 278 | state->c_node_name = strdup(argv[1]); 279 | state->c_node_hostname = strdup(argv[2]); 280 | state->erlang_node_fullname = strdup(argv[3]); 281 | state->erlang_node_cookie = strdup(argv[4]); 282 | ei_set_tracelevel(atoi(argv[5])); 283 | 284 | # ifdef _WIN32 285 | /* Without this, gethostbyname() does not work on Windows... */ 286 | WSAData wsdata; 287 | WSAStartup(WINSOCK_VERSION, &wsdata); 288 | /* Without this, Windows pops up the annoying crash dialog box, 289 | making automated tests impossible. */ 290 | SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX); 291 | # endif 292 | 293 | _connect(state); 294 | 295 | _main_message_loop(state); 296 | 297 | # ifdef _WIN32 298 | WSACleanup(); 299 | # endif 300 | 301 | info_print("C node stopped."); 302 | 303 | free(state); 304 | 305 | return 0; 306 | } 307 | 308 | static void 309 | _connect(struct c_node_state *state) 310 | { 311 | struct hostent *host; 312 | 313 | if ((host = gethostbyname(state->c_node_hostname)) == NULL) { 314 | error_print("Can not resolve host information for %s. (%d)" 315 | ,state->c_node_hostname 316 | ,h_errno); 317 | exit(GCS_EXIT_NO_HOST); 318 | } 319 | 320 | state->c_node_fullname = (char*) 321 | malloc(strlen(state->c_node_name) + strlen(state->c_node_hostname) + 2); 322 | sprintf(state->c_node_fullname, "%s@%s" 323 | ,state->c_node_name 324 | ,state->c_node_hostname); 325 | 326 | struct in_addr *addr = (struct in_addr *) host->h_addr; 327 | 328 | if (ei_connect_xinit(&state->ec 329 | ,state->c_node_hostname 330 | ,state->c_node_name 331 | ,state->c_node_fullname 332 | ,addr 333 | ,state->erlang_node_cookie, 0) < 0) { 334 | error_print("ei_connect_xinit failed: %d (%s)\n" 335 | " host name='%s'\n" 336 | " alive name='%s'\n" 337 | " node name='%s'", 338 | erl_errno,strerror(erl_errno), 339 | state->c_node_hostname, 340 | state->c_node_name, 341 | state->c_node_fullname); 342 | exit(GCS_EXIT_EI_CONNECT_INIT); 343 | } 344 | 345 | info_print("C node '%s' starting.",ei_thisnodename(&state->ec)); 346 | 347 | state->connection_fd = ei_connect(&state->ec,state->erlang_node_fullname); 348 | if (state->connection_fd < 0) { 349 | error_print("ei_connect '%s' failed: %d (%s)", 350 | state->erlang_node_fullname, 351 | erl_errno, strerror(erl_errno)); 352 | exit(GCS_EXIT_EI_CONNECT); 353 | } 354 | 355 | info_print("C node '%s' connected.",ei_thisnodename(&state->ec)); 356 | } 357 | 358 | static void 359 | _reconnect(struct c_node_state *state) 360 | { 361 | if ((state->connection_fd = ei_connect(&state->ec, state->erlang_node_fullname)) < 0) { 362 | error_print("Cannot reconnect to parent node '%s': %d (%s)", 363 | state->erlang_node_fullname, erl_errno, strerror(erl_errno)); 364 | exit(GCS_EXIT_EI_RECONNECT); 365 | } 366 | } 367 | 368 | 369 | /*============================================================================== 370 | * 371 | * Should we be running or not? 372 | * 373 | * Both infinite loops, the message loop and the execution loop, stop when 374 | * this variable is set to zero. 375 | * 376 | *============================================================================*/ 377 | static int running = 1; 378 | 379 | /*============================================================================== 380 | * 381 | * Message queue. 382 | * 383 | * To allow for long-running calls, we need to have our message loop run on the 384 | * main thread, while executions happen on a separate thread. We maintain a 385 | * queue of messages waiting to be processed. The main thread can add to it, 386 | * and the execution thread can remove messages from it. 387 | * 388 | * The execution thread keeps going through messages in the queue until 389 | * there is none left. When that happens, it waits for a new message to arrive, 390 | * and resumes executions. The wait is done when the condition variable 391 | * 'start_executions' is signalled. 392 | * 393 | *============================================================================*/ 394 | struct message { 395 | struct message *next; 396 | erlang_msg msg; 397 | ei_x_buff input; 398 | }; 399 | 400 | static void 401 | message_new(struct message *out) 402 | { 403 | out->next = NULL; 404 | ei_x_new(&out->input); 405 | } 406 | 407 | static void 408 | message_free(struct message *msg) 409 | { 410 | ei_x_free(&msg->input); 411 | free(msg); 412 | } 413 | 414 | static struct message *current_message; 415 | 416 | /* The lock on the list. */ 417 | static pthread_mutex_t message_queue_lock = PTHREAD_MUTEX_INITIALIZER; 418 | 419 | /* The empty list lock and condition variable. */ 420 | static pthread_mutex_t empty_queue_lock = PTHREAD_MUTEX_INITIALIZER; 421 | static pthread_cond_t empty_queue_cond = PTHREAD_COND_INITIALIZER; 422 | 423 | /*============================================================================== 424 | * 425 | * Messages 426 | * 427 | *============================================================================*/ 428 | struct command { 429 | char cmd[MAXATOMLEN_UTF8]; 430 | erlang_pid pid; 431 | const char *args_buff; 432 | const char *state_buff; 433 | const char *from_buff; // The {pid(),Tag} from gen_server:handle_call 434 | int from_buff_len; 435 | }; 436 | 437 | static int 438 | _decode_command(const char* input, struct command* cmd) 439 | { 440 | int index = 0; 441 | char *errmsg; 442 | 443 | memset(cmd,0,sizeof *cmd); /* Reset everything. */ 444 | 445 | # define INV_TUPLE(msg) { errmsg = msg; goto invalid_command_tuple; } 446 | 447 | int ndecoded = gcs_decode(input,&index,"v{apttt}", 448 | (int*)0,5, 449 | cmd->cmd, 450 | &cmd->pid, 451 | &cmd->args_buff, 452 | &cmd->state_buff, 453 | &cmd->from_buff); 454 | 455 | if (ndecoded == 0) INV_TUPLE("Invalid version in message buffer."); 456 | if (ndecoded == 1) INV_TUPLE("Message buffer is not a tuple of size 5."); 457 | if (ndecoded == 2) INV_TUPLE("First tuple element is not an atom."); 458 | if (ndecoded == 3) INV_TUPLE("Second tuple element is not a PID."); 459 | if (ndecoded == 4) INV_TUPLE("Third tuple element is not an erlang term."); 460 | if (ndecoded == 5) INV_TUPLE("Fourth tuple element is not an erlang term."); 461 | if (ndecoded == 6) INV_TUPLE("Fifth tuple element is not an erlang term."); 462 | 463 | cmd->from_buff_len = (input+index)-(cmd->from_buff); 464 | 465 | # undef INV_TUPLE 466 | 467 | return index; 468 | 469 | invalid_command_tuple: 470 | { 471 | index = 0; 472 | int version; 473 | char *err_buff = NULL; 474 | 475 | if (ei_decode_version(input,&index,&version) < 0 476 | || ei_s_print_term(&err_buff,input,&index) < 0) 477 | error_print("%s\nInvalid term. What are you doing?",errmsg); 478 | else 479 | error_print("%s\nMessage: %s",errmsg,err_buff); 480 | if (err_buff!=NULL) free(err_buff); 481 | 482 | memset(cmd,0,sizeof *cmd); /* Reset everything. */ 483 | return -1; 484 | } 485 | } 486 | 487 | /*============================================================================== 488 | * 489 | * The execution thread function. 490 | * 491 | *============================================================================*/ 492 | static void* 493 | _execution_loop(void *arg) 494 | { 495 | struct c_node_state *state = (struct c_node_state*) arg; 496 | 497 | ei_x_buff reply_buffer; 498 | ei_x_new(&reply_buffer); 499 | 500 | ei_x_buff out_msg_buffer; 501 | ei_x_new(&out_msg_buffer); 502 | 503 | while(running) { 504 | /* Wait for a message to be added to the queue. */ 505 | gcs_pthread_mutex_lock(empty_queue_lock); 506 | while (current_message == NULL) 507 | pthread_cond_wait(&empty_queue_cond,&empty_queue_lock); 508 | pthread_mutex_unlock(&empty_queue_lock); 509 | 510 | /* Since this is the only function that drops messages from the queue, 511 | * we don't need to worry about current_message becoming NULL or 512 | * otherwise changing behind our backs. */ 513 | while (current_message != NULL) { 514 | /* XXX process message */ 515 | debug_print("Processing message."); 516 | struct command cmd; 517 | if (_decode_command(current_message->input.buff,&cmd)>0) { 518 | reply_buffer.index=0; 519 | debug_print("Message command: '%s'",cmd.cmd); 520 | if(!strcmp(cmd.cmd,"init")) { 521 | gcs_init(cmd.args_buff,&reply_buffer); 522 | } 523 | else if(!strcmp(cmd.cmd,"terminate")) { 524 | // The 'terminate' reply is ignored, but we send one 525 | // anyway. 526 | gcs_terminate(cmd.args_buff, cmd.state_buff); 527 | ei_x_encode_atom(&reply_buffer,"ok"); 528 | running = 0; 529 | } 530 | else if(!strcmp(cmd.cmd,"call")) { 531 | // 532 | // We wrap the gcs_handle_call Reply into a 3-tuple 533 | // {'$gcs_call_reply',From,Reply} 534 | // 535 | ei_x_encode_tuple_header(&reply_buffer,3); 536 | ei_x_encode_atom(&reply_buffer,"$gcs_call_reply"); 537 | ei_x_append_buf(&reply_buffer,cmd.from_buff,cmd.from_buff_len); 538 | gcs_handle_call( 539 | cmd.args_buff, 540 | cmd.from_buff, 541 | cmd.state_buff, 542 | &reply_buffer); 543 | } 544 | else if(!strcmp(cmd.cmd,"cast")) { 545 | // 546 | // We wrap the gcs_handle_cast Reply into a 2-tuple 547 | // {'$gcs_cast_reply',Reply} 548 | // 549 | ei_x_encode_tuple_header(&reply_buffer,2); 550 | ei_x_encode_atom(&reply_buffer,"$gcs_cast_reply"); 551 | gcs_handle_cast( 552 | cmd.args_buff, 553 | cmd.state_buff, 554 | &reply_buffer); 555 | } 556 | else if(!strcmp(cmd.cmd,"info")) { 557 | // 558 | // We wrap the gcs_handle_info Reply into a 2-tuple 559 | // {'$gcs_info_reply',Reply} 560 | // 561 | ei_x_encode_tuple_header(&reply_buffer,2); 562 | ei_x_encode_atom(&reply_buffer,"$gcs_info_reply"); 563 | gcs_handle_info( 564 | cmd.args_buff, 565 | cmd.state_buff, 566 | &reply_buffer); 567 | } 568 | else error_print("Unknown command '%s'",cmd.cmd); 569 | 570 | if(reply_buffer.index > 0) { 571 | debug_print("Sending message reply."); 572 | out_msg_buffer.index = 0; 573 | ei_x_encode_version(&out_msg_buffer); 574 | ei_x_encode_tuple_header(&out_msg_buffer,2); 575 | ei_x_encode_atom(&out_msg_buffer,state->c_node_fullname); 576 | ei_x_append(&out_msg_buffer,&reply_buffer); 577 | 578 | if(ei_send(state->connection_fd, &cmd.pid, 579 | out_msg_buffer.buff, 580 | out_msg_buffer.index) < 0) { 581 | error_print("Could not send message reply."); 582 | exit(GCS_EXIT_EI_SEND); 583 | } 584 | } 585 | } 586 | 587 | debug_print("Finished processing message."); 588 | 589 | /* Drop the first message from the queue. */ 590 | gcs_pthread_mutex_lock(message_queue_lock); 591 | struct message *next_message = current_message->next; 592 | message_free(current_message); 593 | current_message = next_message; 594 | pthread_mutex_unlock(&message_queue_lock); 595 | } 596 | } 597 | 598 | ei_x_free(&reply_buffer); 599 | 600 | debug_print("End of execution loop."); 601 | 602 | /* We ideally should return, but the problem is that we'll get timeouts 603 | * on the Erlang side. The "proper" thing is to get notified on the message 604 | * loop side when the thread ends, but right now it is blocked waiting 605 | * for a message in ei_xreceive_msg. exit(0) will do for now. */ 606 | exit(0); 607 | 608 | return NULL; 609 | } 610 | 611 | /*============================================================================== 612 | * 613 | * The main message loop. 614 | * 615 | * This is executed in the main thread, and its job is basically to keep the 616 | * C node ticking while the execution thread keeps the executions going. It 617 | * replies to internal Erlang messages such as TICK, and delegates the actual 618 | * work to the message queue, where it gets picked up by the execution thread. 619 | * 620 | *============================================================================*/ 621 | static const char *C_NODE_READY_MSG = ".-. . .- -.. -.--"; /* Morse for "ready". */ 622 | static int _process_gen_call(const char*, ei_x_buff*); 623 | 624 | static void 625 | _main_message_loop(struct c_node_state *state) 626 | { 627 | pthread_t execution_thread; 628 | if(pthread_create(&execution_thread,NULL,&_execution_loop,state)!=0) { 629 | error_print("pthread_create failed: %d (%s)", errno, strerror(errno)); 630 | exit(GCS_EXIT_PTHREAD_CREATE); 631 | } 632 | 633 | ei_x_buff msg_buffer; 634 | ei_x_new(&msg_buffer); 635 | 636 | ei_x_buff reply_buffer; 637 | ei_x_new(&reply_buffer); 638 | 639 | int waiting_for_exit = 0; 640 | 641 | printf("%s\n",C_NODE_READY_MSG); fflush(stdout); 642 | 643 | while (running) { // The execution thread will set running=0 644 | erlang_msg msg; 645 | switch (ei_xreceive_msg(state->connection_fd, &msg, &msg_buffer)) { 646 | case ERL_ERROR: 647 | debug_print( 648 | "ei_xreceive_msg failed: %d (%s)", 649 | erl_errno, strerror(erl_errno)); 650 | _reconnect(state); 651 | break; 652 | case ERL_TICK: 653 | trace_print("TICK"); 654 | break; 655 | case ERL_MSG: 656 | switch(msg.msgtype) { 657 | case ERL_LINK: 658 | debug_print("Erlang node linked."); 659 | break; 660 | case ERL_UNLINK: 661 | case ERL_EXIT: 662 | debug_print("C node unlinked; terminating."); 663 | running = 0; 664 | break; 665 | case ERL_SEND: 666 | case ERL_REG_SEND: 667 | { 668 | if (waiting_for_exit) break; 669 | debug_print("Got message."); 670 | 671 | /* Intercept '$gen_call' messages here. */ 672 | if (_process_gen_call(msg_buffer.buff,&reply_buffer)) 673 | { 674 | if(ei_send(state->connection_fd, &msg.from, 675 | reply_buffer.buff, 676 | reply_buffer.index) < 0) { 677 | error_print("Could not send message reply."); 678 | exit(GCS_EXIT_GEN_CALL_SEND); 679 | } 680 | break; 681 | } 682 | 683 | /* The 'terminate' call is special: we will send 684 | * the message and give the thread a chance to 685 | * exit, but we will stop processing messages 686 | * until that happens. (If we did not have to 687 | * keep replying TICKs, we could have pthread_joined 688 | * here.) */ 689 | struct command cmd; 690 | if (_decode_command(msg_buffer.buff,&cmd)<0) break; 691 | if (!strcmp(cmd.cmd,"terminate")) 692 | waiting_for_exit = 1; 693 | 694 | /* Deep copy message. */ 695 | struct message* new_message = (struct message*)malloc(sizeof *new_message); 696 | message_new(new_message); 697 | erlang_msg_dup(&msg,&new_message->msg); 698 | ei_x_buff_dup(&msg_buffer,&new_message->input); 699 | 700 | /* Add the message to the queue, signalling in case it is 701 | * the first one. Once it is added there, the execution loop 702 | * will take care of it at some point, so we're free to 703 | * get back to responding to ticks. */ 704 | pthread_mutex_lock(&message_queue_lock); 705 | 706 | if (current_message == NULL) { 707 | /* We are the only ones adding to the queue, so we 708 | * don't need to worry about current_message becoming 709 | * non-NULL between the test above and our insertion 710 | * below. */ 711 | pthread_mutex_lock(&empty_queue_lock); 712 | trace_print("Signaling non-empty message queue."); 713 | pthread_cond_signal(&empty_queue_cond); 714 | current_message = new_message; 715 | pthread_mutex_unlock(&empty_queue_lock); 716 | } 717 | else { 718 | struct message *last_message; 719 | for(last_message=current_message; 720 | last_message->next != NULL; 721 | last_message=last_message->next); 722 | last_message->next = new_message; 723 | } 724 | 725 | pthread_mutex_unlock(&message_queue_lock); 726 | } 727 | break; 728 | } 729 | break; 730 | } 731 | } 732 | 733 | /* The following statements are usually not called, since the execution 734 | * thread will exit(0) once "terminate" is received. They are left here 735 | * because if a proper signalling mechanism is implemented, they are what 736 | * is required for a "clean" exit. */ 737 | ei_x_free(&msg_buffer); 738 | ei_x_free(&reply_buffer); 739 | pthread_join(execution_thread,NULL); // Should have finished already. 740 | } 741 | 742 | static int 743 | _process_gen_call(const char* gen_call_msg, ei_x_buff* reply) 744 | { 745 | int ndecoded; 746 | erlang_ref ref; 747 | const char* msg; 748 | 749 | /* Decode {'$gen_call', {_,Ref}, Msg} */ 750 | ndecoded = gcs_decode(gen_call_msg,(int*)0, 751 | "v{A{_r}t}", 752 | (int*)0, /* version */ 753 | 3, /* tuple size {'$gen_call',From,Msg} */ 754 | "$gen_call", /* required atom */ 755 | 2, /* tuple size {_,Ref}=From */ 756 | &ref, /* reference */ 757 | &msg); /* message */ 758 | 759 | if (ndecoded != 9) return 0; 760 | 761 | reply->index = 0; 762 | 763 | /* net_adm:ping 764 | * Msg = {is_auth,_} 765 | * Reply = {Ref,yes} 766 | */ 767 | if (gcs_decode(msg,(int*)0,"{A",0,"is_auth") == 2) { 768 | ei_x_encode_version(reply); 769 | ei_x_encode_tuple_header(reply,2); 770 | ei_x_encode_ref(reply,&ref); 771 | ei_x_encode_atom(reply,"yes"); 772 | return 1; 773 | } 774 | 775 | /* A '$gen_call' we don't understand. */ 776 | int index = 0; 777 | char *err_buff = NULL; 778 | int version; 779 | ei_decode_version(gen_call_msg,&index,&version); 780 | ei_s_print_term(&err_buff,gen_call_msg,&index); 781 | error_print("Unprocessed `$gen_call' message\nMessage %s",err_buff); 782 | free(err_buff); 783 | exit(GCS_EXIT_GEN_CALL); 784 | 785 | return 0; 786 | } 787 | -------------------------------------------------------------------------------- /ebin/gen_c_server.app: -------------------------------------------------------------------------------- 1 | {application, gen_c_server, 2 | [{description, "A generic server for C nodes"}, 3 | {registered, []}, 4 | {applications, 5 | [kernel, 6 | stdlib 7 | ]}, 8 | {env,[]}, 9 | {modules, []}, 10 | 11 | {maintainers, []}, 12 | {licenses, []}, 13 | {links, []} 14 | ]}. 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccrusius/gen_c_server/486ed4290d2b03b199bb8cfb2dbf67a07f42c544/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Sep 15 10:15:20 PDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 165 | if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then 166 | cd "$(dirname "$0")" 167 | fi 168 | 169 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 170 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /include/gen_c_server.h: -------------------------------------------------------------------------------- 1 | #ifndef __GEN_C_SERVER_H__ 2 | #define __GEN_C_SERVER_H__ 3 | /* 4 | %CopyrightBegin% 5 | 6 | Copyright Cesar Crusius 2016. All Rights Reserved. 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | 20 | %CopyrightEnd% 21 | */ 22 | 23 | #include "ei.h" 24 | 25 | /* 26 | * Callback to initialize the C node. 27 | * 28 | * @param args_buff The buffer containing arguments used in the init call from 29 | * Erlang's side of things. They can be retrieved with the ei_decode 30 | * family of functions. 31 | * @param reply The buffer where the gcs_init function should store its reply 32 | * by calling the appropriate ei_x_encode functions. ei_x_encode_version 33 | * will be called automatically, meaning the function should not call it 34 | * itself. 35 | */ 36 | void gcs_init(const char* args_buff, ei_x_buff* reply); 37 | 38 | /* 39 | * Callback to handle gen_c_server:calls. 40 | * 41 | * @param request_buff The buffer containing the call's request. 42 | * @param from_buff The buffer containing the From element of Erlang's 43 | * gen_server:handle_call. 44 | * @param state_buff The buffer containing the C node state. 45 | * @param reply The buffer where the function should store its reply by calling 46 | * the appropriate ei_x_encode functions. ei_x_encode_version will be 47 | * called automatically, meaning the function should not call it itself. 48 | */ 49 | void gcs_handle_call( 50 | const char *request_buff, 51 | const char *from_buff, 52 | const char *state_buff, 53 | ei_x_buff *reply); 54 | 55 | /* 56 | * Callback to handle gen_c_server:casts. 57 | * 58 | * @param request_buff The buffer containing the cast's request. 59 | * @param state_buff The buffer containing the C node state. 60 | * @param reply The buffer where the function should store its reply 61 | * by calling the appropriate ei_x_encode functions. ei_x_encode_version 62 | * will be called automatically, meaning the function should not call it 63 | * itself. 64 | */ 65 | void gcs_handle_cast( 66 | const char *request_buff, 67 | const char *state_buff, 68 | ei_x_buff *reply); 69 | 70 | /* 71 | * Callback to handle gen_c_server:infos. 72 | * 73 | * @param info_buff The buffer containing the info's message. 74 | * @param state_buff The buffer containing the C node state. 75 | * @param reply The buffer where the function should store its reply 76 | * by calling the appropriate ei_x_encode functions. ei_x_encode_version 77 | * will be called automatically, meaning the function should not call it 78 | * itself. 79 | */ 80 | void gcs_handle_info( 81 | const char *info_buff, 82 | const char *state_buff, 83 | ei_x_buff *reply); 84 | 85 | void gcs_terminate(const char *reason_buff, const char *state_buff); 86 | 87 | /* 88 | * Decode a buffer based on the given format. 89 | * 90 | * The extra arguments are the destination for the decoded elements - or, in 91 | * some cases, the detailed specification of what is required. 92 | * 93 | * Format | Decode | Argument 94 | * -------+---------+--------------------------------------------------------- 95 | * a | atom | char* : Atom (must have enough space) 96 | * A | atom | const char* : The desired atom value 97 | * l | long | long* : Long 98 | * p | pid | erlang_pid* : Pid 99 | * r | ref | erlang_ref* : Reference 100 | * t | term | const char** : Term buffer 101 | * v | version | int* : Version 102 | * { | tuple | int : Number of expected elements (0 for any) 103 | * } | nothing | none : This format is for readability purposes 104 | * _ | term | none : Decoded term is simply skipped. 105 | * 106 | * Returns how many characters from the format string have been successfully 107 | * processed. 108 | */ 109 | int gcs_decode(const char* buffer, int *index, const char* fmt, ...); 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /pthreads-win32/README.md: -------------------------------------------------------------------------------- 1 | This is the destination directory for the `pthreads-win32` pre-compiled 2 | distribution. Place the contents of that distribution here if you want to 3 | compile this project in Windows. 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name="gen_c_server" 2 | 3 | -------------------------------------------------------------------------------- /src/gen_c_server.erl: -------------------------------------------------------------------------------- 1 | %%============================================================================== 2 | %% 3 | %% %CopyrightBegin% 4 | %% 5 | %% Copyright Cesar Crusius 2016. All Rights Reserved. 6 | %% 7 | %% Licensed under the Apache License, Version 2.0 (the "License"); 8 | %% you may not use this file except in compliance with the License. 9 | %% You may obtain a copy of the License at 10 | %% 11 | %% http://www.apache.org/licenses/LICENSE-2.0 12 | %% 13 | %% Unless required by applicable law or agreed to in writing, software 14 | %% distributed under the License is distributed on an "AS IS" BASIS, 15 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | %% See the License for the specific language governing permissions and 17 | %% limitations under the License. 18 | %% 19 | %% %CopyrightEnd% 20 | %% 21 | %%============================================================================== 22 | %% 23 | %% == Internals == 24 | %% 25 | %% The `gen_c_server' Erlang module acts as a proxy between a `gen_server' and 26 | %% a C node. In other words, it works by using OTP's `gen_server' to start a 27 | %% `gen_c_server' instance, and this instance will in turn start the C node and 28 | %% forward everything `gen_server' sends its way to the C node. 29 | %% 30 | %% The C node executable is started and connected to an Erlang Port, which 31 | %% then receives as messages whatever the C node prints to `stdout'. The C 32 | %% node itself then uses a convention to send some messages to the Erlang 33 | %% `gen_c_server' process by printing them to `stdout'. In order to 34 | %% differentiate between these messages and whatever else the executable prints 35 | %% out, messages meant to `gen_c_server' are enclosed between special markers. 36 | %% Anything the Erlang side of `gen_c_server' receives outside of these markers 37 | %% is immediately printed to Erlang's `stdout'. 38 | %% 39 | %% === The Initialization Flow === 40 | %% 41 | %% If we omit the execution loop that is running on a separate thread (for 42 | %% simplicity of explanation), the internals of starting a C server are 43 | %% as follows: 44 | %% ``` 45 | %% C node C node 46 | %% # gen_c_server module gen_server module libgen_c_server.a user-defined 47 | %% - ------------------- ----------------- ----------------- ------------ 48 | %% 1 gen_c_server:start -----> gen_server:start 49 | %% 2 gen_c_server:init <----- 50 | %% 3 ------------------------> main() 51 | %% 4 <------------------------ 52 | %% 5 ------------------------> msg_loop --------> gcs_init() { 53 | %% 6 <------------------------ <-------- return; 54 | %% 7 ----->} 55 | %% ''' 56 | %%
    57 | %%
  1. `gen_c_server:start' calls `gen_server:start', starting the regular OTP 58 | %% server setup.
  2. 59 | %%
  3. 60 | %% `gen_server:start' calls `gen_c_server:init'. 61 | %%
  4. 62 | %%
  5. 63 | %% Now the fun begins. `gen_c_server:init' determines the parameters to pass 64 | %% to the C node executable, and starts it via an Erlang Port. It then enters 65 | %% a loop, waiting for the C node to print a "ready" message to `stdout'. 66 | %%
  6. 67 | %%
  7. 68 | %% The C node prints the "ready" message to `stdout' and starts its message 69 | %% loop. Now messages can be sent via normal Erlang methods to the C node. 70 | %%
  8. 71 | %%
  9. 72 | %% `gen_c_server:init' receives the "ready" message via `stdout', and sends 73 | %% an `init' message to the C node. `gen_c_server:init' now waits for a reply 74 | %% to come back. The C node's message loop receives the message, and calls 75 | %% the user-defined `gcs_init' function. 76 | %%
  10. 77 | %%
  11. 78 | %% The user-defined `gcs_init' does what it needs to do, fills in a reply 79 | %% structure, and returns. The message loop wraps that reply and sends it 80 | %% back to `gen_c_server:init'. 81 | %%
  12. 82 | %%
  13. 83 | %% `gen_c_server:init' unwraps the reply, and finally returns the result 84 | %% of `gcs_init' to `gen_server'. 85 | %%
  14. 86 | %%
87 | %% If everything went fine, we now have a `gen_server' that is running a C 88 | %% node via the `gen_c_server' proxy. 89 | %% 90 | %% @end 91 | %% 92 | %%============================================================================== 93 | -module(gen_c_server). 94 | -behaviour(gen_server). 95 | 96 | %%% --------------------------------------------------------------------------- 97 | %%% 98 | %%% Public API 99 | %%% 100 | %%% --------------------------------------------------------------------------- 101 | -export([start/3, start/4, 102 | start_link/3, start_link/4, 103 | stop/1, 104 | call/2, call/3, 105 | c_call/2, c_call/3, 106 | cast/2, 107 | c_cast/2, 108 | c_handle_info/2, 109 | c_init/2, c_init/3, c_terminate/2]). 110 | 111 | 112 | %%% --------------------------------------------------------------------------- 113 | %%% 114 | %%% gen_server callbacks 115 | %%% 116 | %%% --------------------------------------------------------------------------- 117 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). 118 | 119 | %%% --------------------------------------------------------------------------- 120 | %%% 121 | %%% Behaviour callbacks 122 | %%% 123 | %%% --------------------------------------------------------------------------- 124 | -type server_state() :: {State :: term(), Opaque :: term()}. 125 | 126 | -callback c_node() -> Path :: term(). 127 | 128 | -callback init(Args :: term(), Opaque :: term()) -> 129 | {ok, ServerState :: server_state()} | 130 | {ok, ServerState :: server_state(), timeout() | hibernate} | 131 | {stop, Reason :: term()} | ignore. 132 | 133 | -callback terminate( 134 | Reason :: (normal | shutdown | {shutdown, term()} | term ()), 135 | ServerState :: server_state()) -> 136 | term(). 137 | 138 | -callback handle_call(Request :: term(), From :: {pid(), Tag :: term()}, 139 | ServerState :: server_state()) -> 140 | {reply, Reply :: term(), NewServerState :: server_state()} | 141 | {reply, Reply :: term(), NewServerState :: server_state(), timeout() | hibernate} | 142 | {noreply, NewServerState :: server_state()} | 143 | {noreply, NewServerState :: server_state(), timeout() | hibernate} | 144 | {stop, Reason :: term(), Reply :: term(), NewServerState :: server_state()} | 145 | {stop, Reason :: term(), NewServerState :: server_state()}. 146 | 147 | -callback handle_cast(Request :: term(), ServerState :: server_state()) -> 148 | {noreply, NewServerState :: server_state()} | 149 | {noreply, NewServerState :: server_state(), timeout() | hibernate} | 150 | {stop, Reason :: term(), NewServerState :: server_state()}. 151 | 152 | -callback handle_info(Info :: timeout | term(), ServerState :: server_state()) -> 153 | {noreply, NewServerState :: server_state()} | 154 | {noreply, NewServerState :: server_state(), timeout() | hibernate} | 155 | {stop, Reason :: term(), NewServerState :: server_state()}. 156 | 157 | 158 | %%%---------------------------------------------------------------------------- 159 | %%% 160 | %%% Internal state 161 | %%% 162 | %%% The internal state is represented by the `state' record. The 163 | %%% `gen_server' state itself is a pair `{State, InternalState}', 164 | %%% where `State' is the user-defined state. 165 | %%% 166 | %%%---------------------------------------------------------------------------- 167 | -record(state, { 168 | mod, % .......... The Erlang module implementing the behaviour 169 | port, % ......... The Erlang port used to communicate with the C node 170 | mbox, % ......... Messages to the C node will be sent here 171 | buffer, % ....... Buffer for stdout capturing 172 | tracelevel % .... The desired trace level 173 | }). 174 | 175 | state_new() -> #state{ 176 | mod = undefined, 177 | port = undefined, 178 | mbox = undefined, 179 | buffer = undefined, 180 | tracelevel = undefined 181 | }. 182 | 183 | state_disconnect({State, InternalState}) -> 184 | {State, InternalState#state{port = undefined, mbox = undefined}}. 185 | state_clearbuffer(State) -> State#state{buffer = undefined}. 186 | 187 | %%%---------------------------------------------------------------------------- 188 | %%% 189 | %%% Debugging/Logging 190 | %%% 191 | %%%---------------------------------------------------------------------------- 192 | log(trace, TraceLevel, Format, Data) 193 | when is_integer(TraceLevel) andalso TraceLevel > 3 -> 194 | io:fwrite(["[TRACE] gen_c_server:" | Format], Data); 195 | log(debug, TraceLevel, Format, Data) 196 | when is_integer(TraceLevel) andalso TraceLevel > 2 -> 197 | io:fwrite(["[DEBUG] gen_c_server:" | Format], Data); 198 | log(info, TraceLevel, Format, Data) 199 | when is_integer(TraceLevel) andalso TraceLevel > 0 -> 200 | io:fwrite([" gen_c_server:" | Format], Data); 201 | log(Level, #state{tracelevel = TraceLevel}, Format, Data) -> 202 | log(Level, TraceLevel, Format, Data); 203 | log(_, _, _, _) -> 204 | ok. 205 | 206 | %%%---------------------------------------------------------------------------- 207 | %%% 208 | %%% Starting 209 | %%% 210 | %%%---------------------------------------------------------------------------- 211 | start(Mod, Args, Options) -> 212 | TraceLevel = proplists:get_value(tracelevel, Options, 1), 213 | gen_server:start(?MODULE 214 | , [Mod, TraceLevel, Args] 215 | , proplists:delete(tracelevel, Options)). 216 | 217 | start(Name, Mod, Args, Options) -> 218 | TraceLevel = proplists:get_value(tracelevel, Options, 1), 219 | gen_server:start(Name 220 | ,?MODULE 221 | , [Mod, TraceLevel, Args] 222 | , proplists:delete(tracelevel, Options)). 223 | 224 | start_link(Mod, Args, Options) -> 225 | TraceLevel = proplists:get_value(tracelevel, Options, 1), 226 | gen_server:start_link(?MODULE 227 | , [Mod, TraceLevel, Args] 228 | , proplists:delete(tracelevel, Options)). 229 | 230 | start_link(Name, Mod, Args, Options) -> 231 | TraceLevel = proplists:get_value(tracelevel, Options,1), 232 | gen_server:start_link(Name 233 | ,?MODULE 234 | , [Mod, TraceLevel, Args] 235 | , proplists:delete(tracelevel, Options)). 236 | 237 | init([Mod, TraceLevel, Args]) -> 238 | log(debug, TraceLevel, "init(~p)~n", [[Mod, TraceLevel, Args]]), 239 | case node_id(Mod) of 240 | {command_not_found, _} = Reply -> {stop, Reply}; 241 | {_Cmd, NodeName, HostName} -> 242 | %% Initialize internal state 243 | MboxAtom = list_to_atom(lists:flatten([NodeName,"@", HostName])), 244 | Mbox = {c_node, MboxAtom}, 245 | InternalState = (state_new())#state{mod = Mod, 246 | tracelevel = TraceLevel, 247 | mbox = Mbox}, 248 | %% Call Mod:init 249 | Mod:init(Args, InternalState) 250 | end. 251 | 252 | c_init(Args, InternalState) -> 253 | c_init(Args, InternalState, spawn_executable). 254 | 255 | c_init(Args, InternalState, SpawnType) -> 256 | process_flag(trap_exit, true), 257 | #state{mod = Mod, mbox = Mbox, tracelevel = TraceLevel} = InternalState, 258 | {Exe, NodeName, HostName} = node_id(Mod), 259 | log(trace, TraceLevel, "node_id={\"~s\",\"~s\",\"~s\"}~n", 260 | [Exe, NodeName, HostName]), 261 | Argv = [NodeName, HostName, atom_to_list(node()), 262 | atom_to_list(erlang:get_cookie()), 263 | integer_to_list(TraceLevel)], 264 | CommonPortSettings = [stream, {line, 100}, stderr_to_stdout, exit_status], 265 | {Cmd, PortSettings} = 266 | case SpawnType of 267 | spawn -> {string:join([Exe | Argv], " "), CommonPortSettings}; 268 | spawn_executable -> {Exe, [{args, Argv} | CommonPortSettings]} 269 | end, 270 | log(debug, TraceLevel, "c_init:cmd=\"~s\"~n", [Cmd]), 271 | Port = open_port({SpawnType, Cmd}, PortSettings), 272 | NewInternalState = InternalState#state{port = Port}, 273 | case wait_for_startup(NewInternalState) of 274 | {stop, Error} -> {stop, Error}; 275 | {ok, State} -> 276 | Mbox ! {init, self(), Args, undefined, undefined}, 277 | case wait_for_init(state_clearbuffer(State)) of 278 | {ok, UserState} -> {ok, {UserState, NewInternalState}}; 279 | {ok, UserState, Flag} -> {ok, {UserState, NewInternalState}, Flag}; 280 | Other -> Other 281 | end 282 | end. 283 | 284 | node_id(Mod) -> 285 | CNode = Mod:c_node(), 286 | case os:find_executable(CNode) of 287 | false -> 288 | {command_not_found, CNode}; 289 | Cmd -> 290 | NodeName = bin_to_hex(crypto:hash(md5, Cmd)), 291 | HostName = string:sub_word(atom_to_list(node()), 2, $@), 292 | {Cmd, NodeName, HostName} 293 | end. 294 | 295 | bin_to_hex(<>) -> io_lib:format("~.16B", [H]) ++ bin_to_hex(T); 296 | bin_to_hex(<<>>) -> []. 297 | 298 | -define(C_NODE_READY_MSG,".-. . .- -.. -.--"). % Morse code for "ready" 299 | % 300 | % This stdout-based hack is too hacky, but I could not make it work by 301 | % waiting for the node to come up using net_adm:ping plus waiting. An example 302 | % on how that should work is in Erlang's source lib/stdlib/test/dets_SUITE.erl, 303 | % in the ensure_node/2 function. It does not work here for some obscure reason. 304 | % Until I can figure that out, the stdout hack stays. 305 | % 306 | wait_for_startup(#state{port = Port, buffer = Buffer}= State) -> 307 | receive 308 | {Port, {data, {eol, ?C_NODE_READY_MSG}}} -> 309 | {ok, state_clearbuffer(State)}; 310 | {Port, {exit_status, N}} -> {stop, {exit_status, N}}; 311 | {Port, {data, Chunk}} -> 312 | wait_for_startup(State#state{ 313 | buffer = update_message_buffer(Buffer, Chunk)}) 314 | end. 315 | 316 | wait_for_init(#state{port = Port, mbox ={_, Id}, buffer = Buffer}= State) -> 317 | receive 318 | {Port, {exit_status, N}} -> {stop, {exit_status, N}}; 319 | {Port, {data, Chunk}} -> 320 | wait_for_init(State#state{ 321 | buffer = update_message_buffer(Buffer, Chunk)}); 322 | {Id, Reply} -> Reply; 323 | Other -> {stop, {invalid_init_reply, Other}} 324 | end. 325 | 326 | %%%---------------------------------------------------------------------------- 327 | %%% 328 | %%% Stopping 329 | %%% 330 | %%% R16's gen_server does not have the stop() function. We go with a 331 | %%% call here. This will be intercepted later on in `handle_call', 332 | %%% which will reply `stop' and trigger the `gen_server' termination 333 | %%% process, which will finally call `terminate' for us. 334 | %%% 335 | %%%---------------------------------------------------------------------------- 336 | stop(ServerRef) -> gen_server:call(ServerRef,'$gcs_stop'). 337 | 338 | terminate(Reason, {_, #state{mod = Mod}} = ServerState) -> 339 | Mod:terminate(Reason, ServerState). 340 | 341 | c_terminate(_Reason, {_State, #state{mbox = undefined}}) -> 342 | ok; 343 | c_terminate(Reason, {State, #state{mbox = Mbox} = InternalState}) -> 344 | Mbox ! {terminate, self(), Reason, State, undefined}, 345 | wait_for_exit(InternalState). 346 | 347 | wait_for_exit(#state{port = Port, buffer = Buffer} = State) -> 348 | receive 349 | {Port, {exit_status, 0}} -> ok; 350 | {Port, {exit_status, _N}} -> ok; 351 | {'EXIT', Port, _Reason} -> ok; 352 | {Port, {data, Chunk}} -> 353 | wait_for_exit(State#state{ 354 | buffer = update_message_buffer(Buffer, Chunk)}); 355 | _Other -> 356 | wait_for_exit(State) 357 | end. 358 | 359 | %%% --------------------------------------------------------------------------- 360 | %%% 361 | %%% Calls 362 | %%% 363 | %%% Both C and Erlang calls are handled by our own handle_call. The 364 | %%% difference is in the parameters it gets in each case. 365 | %%% 366 | %%% --------------------------------------------------------------------------- 367 | 368 | call(ServerRef, Request) -> 369 | %io:fwrite("gen_c_server:call(~w,~w)", [ServerRef, Request]), 370 | gen_server:call(ServerRef, {'$gcs_ecall', Request}). 371 | 372 | call(ServerRef, Request, Timeout) -> 373 | %io:fwrite("gen_c_server:call(~w,~w,~w)", [ServerRef, Request, Timeout]), 374 | gen_server:call(ServerRef, {'$gcs_ecall', Request}, Timeout). 375 | 376 | c_call(ServerRef, Request) -> 377 | gen_server:call(ServerRef, {'$gcs_ccall', Request}). 378 | 379 | c_call(ServerRef, Request, Timeout) -> 380 | %io:fwrite("gen_c_server:c_call(~w,~w,~w)", [ServerRef, Request, Timeout]), 381 | gen_server:call(ServerRef, {'$gcs_ccall', Request}, Timeout). 382 | 383 | %%% 384 | %%% The 'stop' call is generated from our own stop/1. It replies to gen_server 385 | %%% telling it to, well, stop, which will cause a call to terminate, which will 386 | %%% then take care of the C node stopping activities. 387 | %%% 388 | %%% @private 389 | %%% 390 | handle_call('$gcs_stop', _From, State) -> 391 | {stop, normal, ok, State}; 392 | %%% 393 | %%% Call to Erlang callback module 394 | %%% 395 | handle_call({'$gcs_ecall', Request}, From, 396 | {_, #state{mod = Mod}} = ServerState) -> 397 | Mod:handle_call(Request, From, ServerState); 398 | %%% 399 | %%% This is a normal call, which is forwarded to the C node. The expected reply 400 | %%% from the C node is 401 | %%% 402 | %%% {'$gcs_call_reply', From, Reply} 403 | %%% 404 | %%% where 'Reply' is a gen_server-style reply. We have to wait for the reply 405 | %%% here, and block while waiting only for messages we understand. 406 | %%% 407 | handle_call({'$gcs_ccall', Request}, From, 408 | {State, #state{mbox = Mbox} = InternalState} = ServerState) -> 409 | %io:fwrite("handle_call(~w,~w,~w)", [Request, From, State]), 410 | {_, Id} = Mbox, 411 | Mbox ! {call, self(), Request, State, From}, 412 | receive 413 | % 414 | % The C node is gone 415 | % 416 | {_Port, {exit_status, N}} when N =/= 0 -> 417 | {stop, {port_status, N}, ServerState}; 418 | % 419 | % 'reply' 420 | % 421 | {Id,{'$gcs_call_reply', From,{reply, Reply, NewState}}} -> 422 | {reply, Reply,{NewState, InternalState}}; 423 | {Id,{'$gcs_call_reply', From,{reply, Reply, NewState, hibernate}}} -> 424 | {reply, Reply, {NewState, InternalState}, hibernate}; 425 | % 426 | % 'stop' 427 | % 428 | {Id, {'$gcs_call_reply', From, {stop, Reason, Reply, NewState}}} -> 429 | {stop, Reason, Reply, {NewState, InternalState}} 430 | end; 431 | %%% 432 | %%% What the hell is going on here? 433 | %%% 434 | handle_call(_Request, _From, ServerState) -> 435 | %io:fwrite("rogue handle_call(~w,~w,~w)", [Request, From, State]), 436 | {reply, ok, ServerState}. 437 | 438 | %%% --------------------------------------------------------------------------- 439 | %%% 440 | %%% Casts 441 | %%% 442 | %%% --------------------------------------------------------------------------- 443 | 444 | cast(ServerRef, Request) -> 445 | gen_server:cast(ServerRef, {'$gcs_ecast', Request}). 446 | 447 | c_cast(ServerRef, Request) -> 448 | gen_server:cast(ServerRef, {'$gcs_ccast', Request}). 449 | 450 | %%% 451 | %%% Erlang casts 452 | %%% 453 | handle_cast({'$gcs_ecast', Request}, 454 | {_State, #state{mod = Mod}} = ServerState) -> 455 | Mod:handle_cast(Request, ServerState); 456 | %%% 457 | %%% C node casts 458 | %%% 459 | handle_cast({'$gcs_ccast', Request}, 460 | {State, #state{mbox = Mbox} = InternalState} = ServerState) -> 461 | {_, Id} = Mbox, 462 | Mbox ! {cast, self(), Request, State, undefined}, 463 | receive 464 | % 465 | % The C node is gone 466 | % 467 | {_Port, {exit_status, N}} when N =/= 0 -> 468 | {stop, {port_status, N}, ServerState}; 469 | % 470 | % 'noreply' 471 | % 472 | {Id, {'$gcs_cast_reply', {noreply, NewState}}} -> 473 | {noreply, {NewState, InternalState}}; 474 | {Id, {'$gcs_cast_reply', {noreply, NewState, hibernate}}} -> 475 | {noreply, {NewState, InternalState}, hibernate}; 476 | % 477 | % 'stop' 478 | % 479 | {Id, {'$gcs_cast_reply', {stop, Reason, NewState}}} -> 480 | {stop, Reason, {NewState, InternalState}} 481 | end; 482 | %%% 483 | %%% A cast we don't understand. 484 | %%% 485 | handle_cast(_Request, ServerState) -> 486 | %io:fwrite("rogue handle_cast(~w,~w)", [_Request, State]), 487 | {noreply, ServerState}. 488 | 489 | %%% --------------------------------------------------------------------------- 490 | %%% 491 | %%% handle_info 492 | %%% 493 | %%% --------------------------------------------------------------------------- 494 | %%% 495 | %%% handle_info for termination messages 496 | %%% 497 | %%% --------------------------------------------------------------------------- 498 | handle_info({Port, {exit_status, 0}}, {_, #state{port = Port}} = ServerState) -> 499 | error_logger:info_report("C node exiting"), 500 | {stop, normal, state_disconnect(ServerState)}; 501 | handle_info({Port, {exit_status, N}}, {_, #state{port = Port}} = ServerState) -> 502 | error_logger:error_report("C node exiting"), 503 | {stop, {port_status, N}, state_disconnect(ServerState)}; 504 | handle_info({'EXIT', Port, Reason}, {_, #state{port = Port}} = ServerState) -> 505 | error_logger:error_report("C node exiting"), 506 | {stop, {port_exit, Reason}, state_disconnect(ServerState)}; 507 | %%% --------------------------------------------------------------------------- 508 | %%% 509 | %%% handle_info for stdout messages from C node 510 | %%% 511 | %%% --------------------------------------------------------------------------- 512 | handle_info({Port, {data, Chunk}}, 513 | {State, #state{port = Port, buffer = Buffer} = InternalState}) -> 514 | {noreply, {State, 515 | InternalState#state{buffer = update_message_buffer(Buffer, Chunk)}}}; 516 | %%% --------------------------------------------------------------------------- 517 | %%% 518 | %%% Other info: pass it on to the Erlang module callback. 519 | %%% 520 | %%% --------------------------------------------------------------------------- 521 | handle_info(Info, {_, #state{mod = Mod}} = ServerState) -> 522 | Mod:handle_info(Info, ServerState). 523 | 524 | c_handle_info(Info, 525 | {State, #state{mbox = Mbox} = InternalState} = ServerState) -> 526 | {_, Id} = Mbox, 527 | Mbox ! {info, self(), Info, State, undefined}, 528 | receive 529 | % 530 | % The C node is gone 531 | % 532 | {_Port, {exit_status, N}} when N =/= 0 -> 533 | {stop, {port_status, N}, ServerState}; 534 | % 535 | % 'noreply' 536 | % 537 | {Id, {'$gcs_info_reply', {noreply, NewState}}} -> 538 | {noreply, {NewState, InternalState}}; 539 | {Id, {'$gcs_info_reply', {noreply, NewState, hibernate}}} -> 540 | {noreply, {NewState, InternalState}, hibernate}; 541 | % 542 | % 'stop' 543 | % 544 | {Id, {'$gcs_info_reply', {stop, Reason, NewState}}} -> 545 | {stop, Reason, {NewState, InternalState}} 546 | end. 547 | 548 | 549 | %%============================================================================== 550 | %% 551 | %% Message buffer handling 552 | %% 553 | %%============================================================================== 554 | -define(C_NODE_START_MSG,"... - .- .-. -"). % ...... Morse code for "start" 555 | -define(C_NODE_END_MSG, ". -. -.."). % ............ Morse code for "end" 556 | 557 | % 558 | % Buffer not initialized: waiting for C_NODE_START_MSG 559 | % Anything that arrives while the buffer is not initialized just goes 560 | % directly to stdout. 561 | % 562 | update_message_buffer(undefined, {eol, ?C_NODE_START_MSG}) -> []; 563 | update_message_buffer(undefined, {eol, S}) -> io:fwrite("~s~n", [S]), undefined; 564 | update_message_buffer(undefined, {noeol, S}) -> io:fwrite("~s", [S]), undefined; 565 | % 566 | % End of message marker. Process message as it was intended to. 567 | % 568 | update_message_buffer([], {eol, ?C_NODE_END_MSG}) -> undefined; 569 | update_message_buffer(Buffer,{eol, ?C_NODE_END_MSG}) -> 570 | case lists:reverse(Buffer) of 571 | [ "" ++ S | Rest ] -> 572 | error_logger:info_msg(lists:flatten([S|Rest])); 573 | [ "" ++ S | Rest ] -> 574 | error_logger:warning_report(lists:flatten([S|Rest])); 575 | [ "" ++ S | Rest ] -> 576 | error_logger:error_report(lists:flatten([S|Rest])); 577 | Other -> 578 | error_logger:info_report(lists:flatten(Other)) 579 | end, 580 | undefined; 581 | % 582 | % Otherwise: accumulate message in reverse order. 583 | % 584 | update_message_buffer(Buffer,{eol, S}) -> [$\n, S|Buffer]; 585 | update_message_buffer(Buffer,{noeol, S}) -> [S|Buffer]. 586 | 587 | -------------------------------------------------------------------------------- /test/call_counter/c_src/call_counter.c: -------------------------------------------------------------------------------- 1 | #include "gen_c_server.h" 2 | 3 | /* 4 | * State = { ncalls, ncasts, ninfos } 5 | */ 6 | 7 | static void encode_state( 8 | ei_x_buff *reply, 9 | long ncalls, 10 | long ncasts, 11 | long ninfos) 12 | { 13 | ei_x_encode_tuple_header(reply,3); 14 | ei_x_encode_long(reply,ncalls); 15 | ei_x_encode_long(reply,ncasts); 16 | ei_x_encode_long(reply,ninfos); 17 | } 18 | 19 | void gcs_init(const char* args_buff, ei_x_buff *reply) 20 | { 21 | /* Reply: {ok,{0,0,0}} */ 22 | ei_x_encode_tuple_header(reply,2); 23 | ei_x_encode_atom(reply,"ok"); 24 | encode_state(reply,0,0,0); 25 | } 26 | 27 | void gcs_handle_call( 28 | const char *request_buff, 29 | const char *from_buff, 30 | const char *state_buff, 31 | ei_x_buff *reply) 32 | { 33 | long ncalls, ncasts, ninfos; 34 | gcs_decode(state_buff,(int*)0,"{lll}",3,&ncalls,&ncasts,&ninfos); 35 | 36 | /* Reply: {reply,NewState=Reply,{ncalls+1,ncasts,ninfos}=NewState} */ 37 | ei_x_encode_tuple_header(reply,3); 38 | ei_x_encode_atom(reply,"reply"); 39 | encode_state(reply,ncalls+1,ncasts,ninfos); 40 | encode_state(reply,ncalls+1,ncasts,ninfos); 41 | } 42 | 43 | void gcs_handle_cast( 44 | const char *request_buff, 45 | const char *state_buff, 46 | ei_x_buff *reply) 47 | { 48 | long ncalls, ncasts, ninfos; 49 | gcs_decode(state_buff,(int*)0,"{lll}",3,&ncalls,&ncasts,&ninfos); 50 | 51 | /* Reply: {noreply,NewState={ncalls,ncasts+1,ninfos}} */ 52 | ei_x_encode_tuple_header(reply,2); 53 | ei_x_encode_atom(reply,"noreply"); 54 | encode_state(reply,ncalls,ncasts+1,ninfos); 55 | } 56 | 57 | void gcs_handle_info( 58 | const char *info_buff, 59 | const char *state_buff, 60 | ei_x_buff *reply) 61 | { 62 | long ncalls, ncasts, ninfos; 63 | gcs_decode(state_buff,(int*)0,"{lll}",3,&ncalls,&ncasts,&ninfos); 64 | 65 | /* Reply: {noreply,NewState={ncalls,ncasts,ninfos+1}} */ 66 | ei_x_encode_tuple_header(reply,2); 67 | ei_x_encode_atom(reply,"noreply"); 68 | encode_state(reply,ncalls,ncasts,ninfos+1); 69 | } 70 | 71 | void gcs_terminate(const char *reason_buff, const char *state_buff) { } 72 | 73 | -------------------------------------------------------------------------------- /test/call_counter/call_counter_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(call_counter_SUITE). 2 | -behaviour(gen_c_server). 3 | -include_lib("common_test/include/ct.hrl"). 4 | -export([all/0,init_per_suite/1,init_per_testcase/2,end_per_testcase/2]). 5 | 6 | -export([start_then_stop/1, 7 | ping_me/1, 8 | count_calls/1, 9 | parallel_calls/1, 10 | count_casts/1, 11 | parallel_casts/1, 12 | count_infos/1, 13 | parallel_infos/1 14 | ]). 15 | 16 | %%%============================================================================ 17 | %%% 18 | %%% gen_c_server exports 19 | %%% 20 | %%%============================================================================ 21 | -export([ c_node/0, init/2, terminate/2, handle_info/2 ]). 22 | 23 | %%%============================================================================ 24 | %%% 25 | %%% Tests to run 26 | %%% 27 | %%%============================================================================ 28 | all() -> [ start_then_stop, 29 | ping_me, 30 | count_calls, 31 | parallel_calls, 32 | count_casts, 33 | parallel_casts, 34 | count_infos, 35 | parallel_infos 36 | ]. 37 | 38 | %%%============================================================================ 39 | %%% 40 | %%% gen_c_server callbacks 41 | %%% 42 | %%%============================================================================ 43 | c_node() -> 44 | CNode = filename:join([os:getenv("ROOT_DIR"), 45 | "build","install","call_counter","lib", 46 | "call_counter"]). 47 | 48 | init(Args, Opaque) -> 49 | gen_c_server:c_init(Args, Opaque). 50 | 51 | terminate(Reason, ServerState) -> 52 | gen_c_server:c_terminate(Reason, ServerState). 53 | 54 | handle_info(Info, ServerState) -> 55 | gen_c_server:c_handle_info(Info, ServerState). 56 | 57 | %%============================================================================== 58 | %% 59 | %% SETUP AND TEARDOWN 60 | %% 61 | %%============================================================================== 62 | init_per_suite(Config) -> 63 | net_kernel:start([ct,longnames]), 64 | Config. 65 | 66 | init_per_testcase(_TestCase, Config) -> Config. 67 | 68 | end_per_testcase(_TestCase, _Config) -> ok. 69 | 70 | %%============================================================================== 71 | %% 72 | %% TESTS 73 | %% 74 | %%============================================================================== 75 | start_then_stop(_Config) -> 76 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 77 | gen_c_server:stop(Pid). 78 | 79 | ping_me(_Config) -> 80 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 81 | % 82 | % Yes, this is knowing too much about the internal state's structure, but 83 | % I rather do this, and fix it every time it breaks, than to expose the 84 | % internal state in an 'hrl' file other people could think they could use. 85 | % 86 | {_State, {state, _Mod, _Port, {_,Id}, _Buffer, _Trace}} = sys:get_state(Pid), 87 | pong = net_adm:ping(Id), 88 | gen_c_server:stop(Pid). 89 | 90 | count_calls(_Config) -> 91 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 92 | {1,0,0} = gen_c_server:c_call(Pid,ok), 93 | {2,0,0} = gen_c_server:c_call(Pid,[]), 94 | {3,0,0} = gen_c_server:c_call(Pid,{ok}), 95 | gen_c_server:stop(Pid). 96 | 97 | parallel_calls(_Config) -> 98 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 99 | Num = 1000, 100 | Replies = pmap(fun(I)->gen_c_server:c_call(Pid,I) end,lists:duplicate(Num,0)), 101 | Desired = lists:map(fun(I) -> {I,0,0} end, lists:seq(1,Num)), 102 | Desired = lists:sort(Replies), 103 | gen_c_server:stop(Pid). 104 | 105 | count_casts(_Config) -> 106 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 107 | {1,0,0} = gen_c_server:c_call(Pid,ok), 108 | ok = gen_c_server:c_cast(Pid, ok), 109 | ok = gen_c_server:c_cast(Pid, []), 110 | ok = gen_c_server:c_cast(Pid, [ok]), 111 | {2,3,0} = gen_c_server:c_call(Pid,ok), 112 | gen_c_server:stop(Pid). 113 | 114 | parallel_casts(_Config) -> 115 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 116 | Num = 1000, 117 | Replies = pmap(fun(I)->gen_c_server:c_cast(Pid,I) end,lists:duplicate(Num,0)), 118 | Desired = lists:duplicate(Num,ok), 119 | Desired = Replies, 120 | Self = self(), 121 | WaitPid = spawn(fun() -> wait_for_num_casts(Self,Pid,Num) end), 122 | receive {WaitPid,ok} -> gen_c_server:stop(Pid) 123 | after 1000 -> exit(WaitPid,timeout), 124 | {failed, "Timeout waiting for parallel casts"} 125 | end. 126 | 127 | wait_for_num_casts(From,ServerPid,Num) -> 128 | case gen_c_server:c_call(ServerPid,ok) of 129 | {_,Num,_} -> From ! {self(), ok}; 130 | {_,_N,_} -> timer:sleep(50), 131 | %io:fwrite("Want ~w, got ~w~n",[Num,_N]), 132 | wait_for_num_casts(From,ServerPid,Num) 133 | end. 134 | 135 | count_infos(_Config) -> 136 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 137 | {1,0,0} = gen_c_server:c_call(Pid,ok), 138 | Pid ! ok, 139 | Pid ! [], 140 | Pid ! [ok], 141 | {2,0,3} = gen_c_server:c_call(Pid,ok), 142 | gen_c_server:stop(Pid). 143 | 144 | parallel_infos(_Config) -> 145 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 146 | Num = 1000, 147 | Replies = pmap(fun(I)-> Pid ! I, ok end,lists:duplicate(Num,0)), 148 | Desired = lists:duplicate(Num,ok), 149 | Desired = Replies, 150 | Self = self(), 151 | WaitPid = spawn(fun() -> wait_for_num_infos(Self,Pid,Num) end), 152 | receive {WaitPid,ok} -> gen_c_server:stop(Pid) 153 | after 1000 -> exit(WaitPid,timeout), 154 | {failed, "Timeout waiting for parallel infos"} 155 | end. 156 | 157 | wait_for_num_infos(From,ServerPid,Num) -> 158 | case gen_c_server:c_call(ServerPid,ok) of 159 | {_,_,Num} -> From ! {self(), ok}; 160 | {_,_,_N} -> timer:sleep(50), 161 | %io:fwrite("Want ~w, got ~w~n",[Num,_N]), 162 | wait_for_num_infos(From,ServerPid,Num) 163 | end. 164 | 165 | %%============================================================================== 166 | %% 167 | %% PARALLEL MAP 168 | %% 169 | %%============================================================================== 170 | pmap(Fun,List) -> 171 | Self = self(), 172 | %io:fwrite("pmap: self=~w~n",[Self]), 173 | Pids = lists:map(fun(Item) -> 174 | spawn(fun() -> pmap_do(Self,Fun,Item) end) 175 | end, 176 | List), 177 | gather(Pids, []). 178 | 179 | pmap_do(Client,Fun,Item) -> 180 | %io:fwrite("pmap_do(~w,~w,~w): self=~w",[Client,Fun,Item,self()]), 181 | V = Fun(Item), 182 | %io:fwrite("pmap_do(~w,~w): message will be {~w,~w}",[Client,Fun,self(),V]), 183 | Client ! {self(),V}. 184 | 185 | gather([], Acc) -> lists:reverse(Acc); 186 | gather([H|T], Acc) -> 187 | receive 188 | {H,Ret} -> 189 | %io:fwrite("gather(~w) received {~w,~w}",[[H|T],H,Ret]), 190 | gather(T, [Ret|Acc]) 191 | end. 192 | -------------------------------------------------------------------------------- /test/crashy/c_src/crashy.c: -------------------------------------------------------------------------------- 1 | #include "gen_c_server.h" 2 | #include 3 | 4 | /* 5 | * State = undefined 6 | */ 7 | 8 | void gcs_init(const char* args_buff, ei_x_buff *reply) 9 | { 10 | /* Reply: {ok,undefined} */ 11 | ei_x_encode_tuple_header(reply,2); 12 | ei_x_encode_atom(reply,"ok"); 13 | ei_x_encode_atom(reply,"undefined"); 14 | } 15 | 16 | static void maybe_crash(const char *request_buff) 17 | { 18 | long status; 19 | if (gcs_decode(request_buff,(int*)0,"{Al}",2,"stop",&status)==4) 20 | exit(status); 21 | if (gcs_decode(request_buff,(int*)0,"A","segfault")==1) { 22 | int* null=(int*)0; 23 | *null=1; 24 | } 25 | if (gcs_decode(request_buff,(int*)0,"A","abort")==1) 26 | abort(); 27 | } 28 | 29 | void gcs_handle_call( 30 | const char *request_buff, 31 | const char *from_buff, 32 | const char *state_buff, 33 | ei_x_buff *reply) 34 | { 35 | maybe_crash(request_buff); 36 | 37 | /* Reply: {reply,ok=Reply,undefined=NewState} */ 38 | ei_x_encode_tuple_header(reply,3); 39 | ei_x_encode_atom(reply,"reply"); 40 | ei_x_encode_atom(reply,"ok"); 41 | ei_x_encode_atom(reply,"undefined"); 42 | } 43 | 44 | void gcs_handle_cast( 45 | const char *request_buff, 46 | const char *state_buff, 47 | ei_x_buff *reply) 48 | { 49 | maybe_crash(request_buff); 50 | 51 | /* Reply: {noreply,undefined=NewState} */ 52 | ei_x_encode_tuple_header(reply,2); 53 | ei_x_encode_atom(reply,"reply"); 54 | ei_x_encode_atom(reply,"undefined"); 55 | } 56 | 57 | void gcs_handle_info( 58 | const char *info_buff, 59 | const char *state_buff, 60 | ei_x_buff *reply) 61 | { 62 | gcs_handle_cast(info_buff, state_buff, reply); 63 | } 64 | 65 | void gcs_terminate(const char *reason_buff, const char *state_buff) { } 66 | 67 | -------------------------------------------------------------------------------- /test/crashy/crashy_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(crashy_SUITE). 2 | -include_lib("common_test/include/ct.hrl"). 3 | -export([all/0,init_per_suite/1,init_per_testcase/2,end_per_testcase/2]). 4 | 5 | -export([ 6 | start_then_stop/1, 7 | crash_in_call/1, 8 | segfault_in_call/1, 9 | abort_in_call/1, 10 | crash_in_cast/1, 11 | segfault_in_cast/1, 12 | abort_in_cast/1, 13 | crash_in_info/1, 14 | segfault_in_info/1, 15 | abort_in_info/1 16 | ]). 17 | 18 | -export([ c_node/0, init/2, terminate/2, handle_info/2 ]). 19 | 20 | all() -> [ 21 | start_then_stop, 22 | crash_in_call, 23 | segfault_in_call, 24 | abort_in_call, 25 | crash_in_cast, 26 | segfault_in_cast, 27 | abort_in_cast, 28 | crash_in_info, 29 | segfault_in_info, 30 | abort_in_info 31 | ]. 32 | 33 | %%%============================================================================ 34 | %%% 35 | %%% gen_c_server callbacks 36 | %%% 37 | %%%============================================================================ 38 | c_node() -> 39 | filename:join([os:getenv("ROOT_DIR"), 40 | "build","install","crashy","lib", 41 | "crashy"]). 42 | 43 | init(Args, Opaque) -> 44 | gen_c_server:c_init(Args, Opaque). 45 | 46 | terminate(Reason, ServerState) -> 47 | gen_c_server:c_terminate(Reason, ServerState). 48 | 49 | handle_info(Info, ServerState) -> 50 | gen_c_server:c_handle_info(Info, ServerState). 51 | 52 | %%============================================================================== 53 | %% 54 | %% SETUP AND TEARDOWN 55 | %% 56 | %%============================================================================== 57 | init_per_suite(Config) -> 58 | net_kernel:start([ct,longnames]), 59 | Config. 60 | 61 | init_per_testcase(_TestCase, Config) -> Config. 62 | 63 | end_per_testcase(_TestCase, _Config) -> ok. 64 | 65 | %%============================================================================== 66 | %% 67 | %% TESTS 68 | %% 69 | %%============================================================================== 70 | start_then_stop(_Config) -> 71 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 72 | gen_c_server:stop(Pid). 73 | 74 | crash_in_call(_Config) -> 75 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 76 | {'EXIT',{{port_status,10},_}} = (catch gen_c_server:c_call(Pid,{stop,10})), 77 | {'EXIT',{noproc,_}} = (catch gen_c_server:stop(Pid)), 78 | ok. 79 | 80 | segfault_in_call(_Config) -> 81 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 82 | {'EXIT',{{port_status,_},_}} = (catch gen_c_server:c_call(Pid,segfault)), 83 | {'EXIT',{noproc,_}} = (catch gen_c_server:stop(Pid)), 84 | ok. 85 | 86 | abort_in_call(_Config) -> 87 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 88 | {'EXIT',{{port_status,_},_}} = (catch gen_c_server:c_call(Pid,abort)), 89 | {'EXIT',{noproc,_}} = (catch gen_c_server:stop(Pid)), 90 | ok. 91 | 92 | crash_in_cast(_Config) -> 93 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 94 | ok = gen_c_server:c_cast(Pid,{stop,10}), 95 | {'EXIT',{{port_status,10},_}} = (catch gen_c_server:stop(Pid)), 96 | ok. 97 | 98 | segfault_in_cast(_Config) -> 99 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 100 | ok = gen_c_server:c_cast(Pid,segfault), 101 | {'EXIT',{{port_status,_},_}} = (catch gen_c_server:stop(Pid)), 102 | ok. 103 | 104 | abort_in_cast(_Config) -> 105 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 106 | ok = gen_c_server:c_cast(Pid,abort), 107 | {'EXIT',{{port_status,_},_}} = (catch gen_c_server:stop(Pid)), 108 | ok. 109 | 110 | crash_in_info(_Config) -> 111 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 112 | Pid ! {stop,10}, 113 | {'EXIT',{{port_status,10},_}} = (catch gen_c_server:stop(Pid)), 114 | ok. 115 | 116 | segfault_in_info(_Config) -> 117 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 118 | Pid ! segfault, 119 | {'EXIT',{{port_status,_},_}} = (catch gen_c_server:stop(Pid)), 120 | ok. 121 | 122 | abort_in_info(_Config) -> 123 | {ok, Pid} = gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 124 | Pid ! abort, 125 | {'EXIT',{{port_status,_},_}} = (catch gen_c_server:stop(Pid)), 126 | ok. 127 | -------------------------------------------------------------------------------- /test/reply_this/c_src/reply_this.c: -------------------------------------------------------------------------------- 1 | #include "gen_c_server.h" 2 | 3 | void gcs_init(const char* args_buff, ei_x_buff *reply) 4 | { 5 | ei_x_encode_tuple_header(reply,2); 6 | ei_x_encode_atom(reply,"ok"); 7 | ei_x_encode_atom(reply,"undefined"); 8 | } 9 | 10 | void gcs_handle_call( 11 | const char *request_buff, 12 | const char *from_buff, 13 | const char *state_buff, 14 | ei_x_buff *reply) 15 | { 16 | int index = 0; 17 | const char *the_reply; 18 | if(gcs_decode(request_buff,&index,"{At}",2,"reply_this",&the_reply) == 4) 19 | { 20 | int reply_len = request_buff + index - the_reply; 21 | ei_x_append_buf(reply,the_reply,reply_len); 22 | } 23 | else 24 | { 25 | ei_x_encode_tuple_header(reply,3); 26 | ei_x_encode_atom(reply,"reply"); 27 | ei_x_encode_atom(reply,"ok"); 28 | ei_x_encode_atom(reply,"undefined"); 29 | } 30 | } 31 | 32 | void gcs_handle_cast( 33 | const char *request_buff, 34 | const char *state_buff, 35 | ei_x_buff *reply) 36 | { 37 | int index = 0; 38 | const char *the_reply; 39 | if(gcs_decode(request_buff,&index,"{At}",2,"reply_this",&the_reply) == 4) 40 | { 41 | int reply_len = request_buff + index - the_reply; 42 | ei_x_append_buf(reply,the_reply,reply_len); 43 | } 44 | else 45 | { 46 | ei_x_encode_tuple_header(reply,2); 47 | ei_x_encode_atom(reply,"noreply"); 48 | ei_x_encode_atom(reply,"undefined"); 49 | } 50 | } 51 | 52 | void gcs_handle_info( 53 | const char *info_buff, 54 | const char *state_buff, 55 | ei_x_buff *reply) 56 | { 57 | gcs_handle_cast(info_buff, state_buff, reply); 58 | } 59 | 60 | void gcs_terminate(const char *reason_buff, const char *state_buff) { } 61 | 62 | -------------------------------------------------------------------------------- /test/reply_this/reply_this_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(reply_this_SUITE). 2 | -include_lib("common_test/include/ct.hrl"). 3 | -export([all/0,init_per_suite/1,init_per_testcase/2,end_per_testcase/2]). 4 | 5 | -export([ 6 | normal_replies/1, 7 | timeout_replies/1, 8 | call_stop/1, 9 | cast_stop/1, 10 | info_stop/1 11 | ]). 12 | 13 | -export([ c_node/0, init/2, terminate/2, handle_info/2 ]). 14 | 15 | all() -> [ 16 | normal_replies, 17 | timeout_replies, 18 | call_stop, 19 | cast_stop, 20 | info_stop 21 | ]. 22 | 23 | %%% --------------------------------------------------------------------------- 24 | %%% 25 | %%% custom_gen_c_server callbacks 26 | %%% 27 | %%% This test suite uses a "custom" `gen_c_server' that was produced 28 | %%% by the build system by renaming the module. 29 | %%% 30 | %%% --------------------------------------------------------------------------- 31 | c_node() -> 32 | filename:join([os:getenv("ROOT_DIR"), 33 | "build","install","reply_this","lib", 34 | "reply_this"]). 35 | 36 | init(Args, Opaque) -> 37 | custom_gen_c_server:c_init(Args, Opaque, spawn). 38 | 39 | terminate(Reason, ServerState) -> 40 | custom_gen_c_server:c_terminate(Reason, ServerState). 41 | 42 | handle_info(Info, ServerState) -> 43 | custom_gen_c_server:c_handle_info(Info, ServerState). 44 | 45 | 46 | %%============================================================================== 47 | %% 48 | %% SETUP AND TEARDOWN 49 | %% 50 | %%============================================================================== 51 | init_per_suite(Config) -> 52 | net_kernel:start([ct,longnames]), 53 | Config. 54 | 55 | init_per_testcase(_TestCase, Config) -> Config. 56 | 57 | end_per_testcase(_TestCase, _Config) -> 58 | ok. 59 | 60 | %%============================================================================== 61 | %% 62 | %% TESTS 63 | %% 64 | %%============================================================================== 65 | normal_replies(_Config) -> 66 | {ok, Pid} = custom_gen_c_server:start(?MODULE,[],[{tracelevel,10}]), 67 | 10 = custom_gen_c_server:c_call(Pid,{reply_this,{reply,10,undefined}}), 68 | ok = custom_gen_c_server:c_call(Pid,{reply_this,{reply,ok,[ok]}}), 69 | [] = custom_gen_c_server:c_call(Pid,{reply_this,{reply,[],10}}), 70 | ok = custom_gen_c_server:c_cast(Pid,{reply_this,{noreply,undefined}}), 71 | Pid ! {reply_this,{noreply,undefined}}, 72 | custom_gen_c_server:stop(Pid). 73 | 74 | timeout_replies(_Config) -> 75 | {ok, Pid} = custom_gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 76 | ok = custom_gen_c_server:c_cast(Pid,{reply_this,{noreply,undefined,hibernate}}), 77 | 10 = custom_gen_c_server:c_call(Pid,{reply_this,{reply,10,undefined,hibernate}}), 78 | Pid ! {reply_this,{noreply,undefined,hibernate}}, 79 | custom_gen_c_server:stop(Pid). 80 | 81 | call_stop(_Config) -> 82 | {ok, Pid} = custom_gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 83 | 10 = custom_gen_c_server:c_call(Pid,{reply_this,{stop,"I was asked to",10,undefined}}), 84 | case (catch custom_gen_c_server:stop(Pid)) of 85 | {'EXIT',{noproc,_}} -> ok; 86 | {'EXIT',{"I was asked to",_}} -> ok 87 | end. 88 | 89 | cast_stop(_Config) -> 90 | {ok, Pid} = custom_gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 91 | ok = custom_gen_c_server:c_cast(Pid,{reply_this,{stop,"I was asked to",undefined}}), 92 | case (catch custom_gen_c_server:stop(Pid)) of 93 | {'EXIT',{noproc,_}} -> ok; 94 | {'EXIT',{"I was asked to",_}} -> ok 95 | end. 96 | 97 | info_stop(_Config) -> 98 | {ok, Pid} = custom_gen_c_server:start(?MODULE,[],[{tracelevel,0}]), 99 | Pid ! {reply_this,{stop,"I was asked to",undefined}}, 100 | case (catch custom_gen_c_server:stop(Pid)) of 101 | {'EXIT',{noproc,_}} -> ok; 102 | {'EXIT',{"I was asked to",_}} -> ok 103 | end. 104 | 105 | --------------------------------------------------------------------------------