├── .gitignore ├── .gitmodules ├── .travis.yml ├── MANIFEST ├── MANIFEST.SKIP ├── META.json ├── META.yml ├── Makefile.PL ├── README ├── README.pod ├── Tarantool16.xs ├── Vagrantfile ├── bench ├── bench.pl ├── lib │ ├── Inserter.pm │ ├── Selector.pm │ └── Util.pm └── test.pl ├── build.sh ├── lib └── EV │ ├── Tarantool16.pm │ └── Tarantool16 │ └── Multi.pm ├── libs └── crypto │ ├── base64.c │ ├── base64.h │ ├── sha1.c │ └── sha1.h ├── makeall.sh ├── ppport.h ├── provision ├── travis.sh └── vagrant.sh ├── rpm ├── perl-EV-Tarantool16.spec ├── prebuild-el-7.sh └── prebuild-el.sh ├── t ├── 00-use.t ├── 01-basic.t ├── 02-initial.t ├── 03-connected.t ├── 04-writeio.t ├── 05-auth.t ├── 06-timeout.t ├── 07-memory.t ├── 08-multi.t ├── lib │ └── Renewer.pm └── tnt │ ├── app.lua │ └── init.lua ├── temp ├── dbg.pl ├── demo.pl ├── init.lua └── leaks.pl └── xstarantool ├── encdec.h ├── endian_compat.h ├── log.h ├── types.h ├── xsmy.h └── xstnt16.h /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | /*.c 3 | 4 | /blib/ 5 | /.build/ 6 | _build/ 7 | cover_db/ 8 | inc/ 9 | Build 10 | !Build/ 11 | Build.bat 12 | .last_cover_stats 13 | /Makefile 14 | /Makefile.old 15 | /MANIFEST.bak 16 | /MYMETA.* 17 | nytprof.out 18 | /pm_to_blib 19 | *.o 20 | *.bs 21 | /_eumm/ 22 | 23 | *.tar.gz 24 | .lwpcookies 25 | Kit.pm 26 | *.swp 27 | *.src.rpm 28 | dist/ 29 | tnt_* 30 | 31 | EV-Tarantool16-*/ 32 | .vscode 33 | packpack 34 | build 35 | *.snap 36 | *.xlog 37 | tarantool.log 38 | *.pid 39 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "evcnn"] 2 | path = libs/evcnn 3 | url = https://github.com/igorcoding/libevconnection.git 4 | [submodule "msgpuck"] 5 | path = libs/msgpuck 6 | url = https://github.com/tarantool/msgpuck.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: C 3 | services: 4 | - docker 5 | 6 | cache: 7 | directories: 8 | - $HOME/.cache 9 | 10 | env: 11 | global: 12 | - PRODUCT=EV-Tarantool16 13 | - ARCH=x86_64 14 | matrix: 15 | - OS=el DIST=7 VAR_TARANTOOL=1.6 16 | - OS=el DIST=7 VAR_TARANTOOL=1.7 17 | - OS=el DIST=7 VAR_TARANTOOL=1.9 18 | 19 | script: 20 | - git describe --long 21 | - git clone -b prebuild_fixes https://github.com/igorcoding/packpack.git packpack 22 | - packpack/packpack 23 | 24 | before_deploy: 25 | - ls -l build/ 26 | 27 | deploy: 28 | # Deploy packages to PackageCloud 29 | - provider: packagecloud 30 | username: igorcoding 31 | repository: "tarantoolcontrib" 32 | token: ${PACKAGECLOUD_TOKEN} 33 | dist: ${OS}/${DIST} 34 | package_glob: build/*.{rpm,deb,dsc} 35 | skip_cleanup: true 36 | on: 37 | branch: master 38 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" && "${VAR_TARANTOOL}" = "1.9" 39 | 40 | notifications: 41 | email: true 42 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | lib/EV/Tarantool16.pm 2 | lib/EV/Tarantool16/Multi.pm 3 | libs/crypto/base64.c 4 | libs/crypto/base64.h 5 | libs/crypto/sha1.c 6 | libs/crypto/sha1.h 7 | libs/evcnn/CMakeLists.txt 8 | libs/evcnn/ex/sample.c 9 | libs/evcnn/src/evconnection.h 10 | libs/evcnn/src/libevconnection.c 11 | libs/evcnn/src/xsevcnn.h 12 | libs/msgpuck/hints.c 13 | libs/msgpuck/msgpuck.c 14 | libs/msgpuck/msgpuck.h 15 | Makefile.PL 16 | MANIFEST 17 | MANIFEST.SKIP 18 | ppport.h 19 | README 20 | README.pod 21 | t/00-use.t 22 | t/01-basic.t 23 | t/02-initial.t 24 | t/03-connected.t 25 | t/04-writeio.t 26 | t/05-auth.t 27 | t/06-timeout.t 28 | t/07-memory.t 29 | t/08-multi.t 30 | t/lib/Renewer.pm 31 | t/tnt/app.lua 32 | t/tnt/init.lua 33 | Tarantool16.xs 34 | xstarantool/encdec.h 35 | xstarantool/endian_compat.h 36 | xstarantool/log.h 37 | xstarantool/types.h 38 | xstarantool/xsmy.h 39 | xstarantool/xstnt16.h 40 | -------------------------------------------------------------------------------- /MANIFEST.SKIP: -------------------------------------------------------------------------------- 1 | # Avoid version control files. 2 | \B\.git\b 3 | 4 | # Avoid Makemaker generated and utility files. 5 | \bMANIFEST\.bak 6 | \bMakefile$ 7 | \bblib/ 8 | \bMakeMaker-\d 9 | \bpm_to_blib\.ts$ 10 | \bpm_to_blib$ 11 | \bblibdirs\.ts$ # 6.18 through 6.25 generated this 12 | 13 | # Avoid Module::Build generated and utility files. 14 | \bBuild$ 15 | \b_build/ 16 | 17 | # Avoid temp and backup files. 18 | ~$ 19 | \.old$ 20 | \#$ 21 | \b\.# 22 | \.bak$ 23 | _bak$ 24 | Tarantool16.c 25 | Tarantool16.o 26 | hints.o 27 | msgpuck.o 28 | Tarantool16.bs 29 | 30 | # Avoid Devel::Cover files. 31 | \bcover_db\b 32 | 33 | # Avoid local testing/dist files 34 | ^MYMETA\. 35 | ^lib/.+\.pl$ 36 | ^dist/ 37 | ^makeall\.sh$ 38 | ^tmp/ 39 | ^EV-Tarantool16-.* 40 | 41 | # msgpuck 42 | ^libs/msgpuck/.build.mk 43 | ^libs/msgpuck/AUTHORS 44 | ^libs/msgpuck/CMakeLists.txt 45 | ^libs/msgpuck/debian/ 46 | ^libs/msgpuck/Doxyfile.in 47 | ^libs/msgpuck/Jenkinsfile 48 | ^libs/msgpuck/LICENSE 49 | ^libs/msgpuck/README.md 50 | ^libs/msgpuck/rpm/msgpuck.spec 51 | ^libs/msgpuck/test/ 52 | 53 | 54 | # Custom 55 | ^.vagrant/ 56 | \.gitignore 57 | \.gitmodules 58 | build.sh 59 | \.travis.yml 60 | Vagrantfile 61 | ^tnt_* 62 | ^\.tnt* 63 | ^provision/ 64 | ^temp/ 65 | ^bench/ 66 | ^.vscode/ 67 | ^packpack 68 | ^build 69 | ^rpm 70 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "abstract" : "EV client for Tarantool 1.6", 3 | "author" : [ 4 | "Mons Anderson ", 5 | "igorcoding " 6 | ], 7 | "dynamic_config" : 0, 8 | "generated_by" : "ExtUtils::MakeMaker version 7.0401, CPAN::Meta::Converter version 2.150001", 9 | "license" : [ 10 | "open_source" 11 | ], 12 | "meta-spec" : { 13 | "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", 14 | "version" : "2" 15 | }, 16 | "name" : "EV-Tarantool16", 17 | "no_index" : { 18 | "directory" : [ 19 | "t", 20 | "inc" 21 | ] 22 | }, 23 | "prereqs" : { 24 | "build" : { 25 | "requires" : { 26 | "AnyEvent" : "0", 27 | "Carp" : "0", 28 | "Data::Dumper" : "0", 29 | "Proc::ProcessTable" : "0", 30 | "Scalar::Util" : "0", 31 | "Test::Deep" : "0", 32 | "Test::More" : "0", 33 | "Test::Tarantool16" : "0", 34 | "Time::HiRes" : "0", 35 | "constant" : "0" 36 | } 37 | }, 38 | "configure" : { 39 | "requires" : { 40 | "ExtUtils::MakeMaker" : "0" 41 | } 42 | }, 43 | "runtime" : { 44 | "requires" : { 45 | "EV" : "4", 46 | "Types::Serialiser" : "0" 47 | } 48 | } 49 | }, 50 | "release_status" : "stable", 51 | "version" : "1.39" 52 | } 53 | -------------------------------------------------------------------------------- /META.yml: -------------------------------------------------------------------------------- 1 | --- 2 | abstract: 'EV client for Tarantool 1.6' 3 | author: 4 | - 'Mons Anderson ' 5 | - 'igorcoding ' 6 | build_requires: 7 | AnyEvent: '0' 8 | Carp: '0' 9 | Data::Dumper: '0' 10 | Proc::ProcessTable: '0' 11 | Scalar::Util: '0' 12 | Test::Deep: '0' 13 | Test::More: '0' 14 | Test::Tarantool16: '0' 15 | Time::HiRes: '0' 16 | constant: '0' 17 | configure_requires: 18 | ExtUtils::MakeMaker: '0' 19 | dynamic_config: 0 20 | generated_by: 'ExtUtils::MakeMaker version 7.0401, CPAN::Meta::Converter version 2.150001' 21 | license: open_source 22 | meta-spec: 23 | url: http://module-build.sourceforge.net/META-spec-v1.4.html 24 | version: '1.4' 25 | name: EV-Tarantool16 26 | no_index: 27 | directory: 28 | - t 29 | - inc 30 | requires: 31 | EV: '4' 32 | Types::Serialiser: '0' 33 | version: '1.39' 34 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | use 5.008008; 2 | use ExtUtils::MakeMaker; 3 | 4 | =for rpm 5 | BuildRequires: c-ares19-devel 6 | BuildRequires: libev-devel 7 | Requires: c-ares19 8 | Requires: perl-EV 9 | =cut 10 | 11 | my $preop = 12 | 'perldoc -uT $(VERSION_FROM) | tee $(DISTVNAME)/README.pod > README.pod;' . 13 | 'pod2text README.pod | tee $(DISTVNAME)/README > README'; 14 | 15 | my $ARES_CFLAGS = $ENV{ARES_CFLAGS} // '-I/usr/include'; 16 | my $ARES_LDFLAGS = $ENV{ARES_LDFLAGS} // '-lcares'; 17 | WriteMakefile( 18 | NAME => 'EV::Tarantool16', 19 | AUTHOR => ['Mons Anderson ', 'igorcoding '], 20 | VERSION_FROM => 'lib/EV/Tarantool16.pm', 21 | ABSTRACT_FROM => 'lib/EV/Tarantool16.pm', 22 | PREREQ_PM => { 23 | 'EV' => 4, 24 | 'Types::Serialiser' => 0 25 | }, 26 | BUILD_REQUIRES => { 27 | 'Test::Tarantool16' => 0, 28 | 'Test::More' => 0, 29 | 'Test::Deep' => 0, 30 | 'AnyEvent' => 0, 31 | 'Proc::ProcessTable' => 0, 32 | 'Time::HiRes' => 0, 33 | 'Scalar::Util' => 0, 34 | 'Data::Dumper' => 0, 35 | 'Carp' => 0, 36 | 'constant' => 0, 37 | }, 38 | LIBS => [ $ARES_LDFLAGS ], 39 | DEFINE => '-g -ggdb -O1 -std=c99 -Wall', 40 | LICENSE => 'GPL', 41 | dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', PREOP => $preop }, 42 | CONFIGURE => sub { 43 | require EV::MakeMaker; 44 | return {EV::MakeMaker::ev_args( 45 | INC => '-I. -I./xstarantool -I./libs/evcnn/src -I./libs/msgpuck -I./libs/crypto '. $ARES_CFLAGS, 46 | OBJECT => '$(O_FILES) ./libs/msgpuck/msgpuck.o ./libs/msgpuck/hints.o', 47 | LDFROM => '$(O_FILES) msgpuck.o hints.o' 48 | )}; 49 | }, 50 | ); 51 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | NAME 2 | EV::Tarantool16 - EV client for Tarantool 1.6 3 | 4 | VESRION 5 | Version 1.39 6 | 7 | SYNOPSIS 8 | use EV::Tarantool16; 9 | my $c; $c = EV::Tarantool16->new({ 10 | host => '127.0.0.1', 11 | port => 3301, 12 | username => 'test_user', 13 | password => 'test_passwd', 14 | reconnect => 0.2, 15 | connected => sub { 16 | warn "connected: @_"; 17 | EV::unloop; 18 | }, 19 | connfail => sub { 20 | warn "connfail: @_ / $!"; 21 | EV::unloop; 22 | }, 23 | disconnected => sub { 24 | warn "discon: @_ / $!"; 25 | EV::unloop; 26 | }, 27 | }); 28 | 29 | $c->connect; 30 | EV::loop; 31 | 32 | $c->ping(sub { 33 | my $a = @_[0]; 34 | diag Dumper \@_ if !$a; 35 | EV::unloop; 36 | }); 37 | EV::loop; 38 | 39 | SUBROUTINES/METHODS 40 | new {option => value,...} 41 | Create new EV::Tarantool16 instance. 42 | 43 | host => $address 44 | Address connect to. 45 | 46 | port => $port 47 | Port connect to. 48 | 49 | username => $username 50 | Username. 51 | 52 | password => $password 53 | Password. 54 | 55 | reconnect => $reconnect 56 | Reconnect timeout. 57 | 58 | log_level => $log_level 59 | Logging level. Values: (0: None), (1: Error), (2: Warning), (3: 60 | Info), (4: Debug) 61 | 62 | cnntrace => $cnntrace 63 | Enable (1) or disable(0) evcnn tracing. 64 | 65 | ares_reuse => $ares_reuse 66 | Enable (1) or disable(0) c-ares connection reuse (default = 0). 67 | 68 | wbuf_limit => $wbuf_limit 69 | Write vector buffer length limit. Defaults to 16384. Set wbuf_limit 70 | = 0 to disable write buffer length check on every request. 71 | 72 | connected => $sub 73 | Called when connection to Tarantool 1.6 instance is established, 74 | authenticated successfully and retrieved spaces information from it. 75 | 76 | connfail => $sub 77 | Called when connection to Tarantool 1.6 instance failed. 78 | 79 | disconnected => $sub 80 | Called when Tarantool 1.6 instance disconnected. 81 | 82 | connect 83 | Connect to Tarantool 1.6 instance. EV::Tarantool16->connected is called 84 | when connection is established. 85 | 86 | disconnect 87 | Disconnect from Tarantool 1.6 instance. EV::Tarantool16->disconnected is 88 | called afterwards. 89 | 90 | ping $opts, $cb->($result) 91 | Execute ping request 92 | 93 | $opts 94 | HASHREF of additional options to the request 95 | 96 | timeout => $timeout 97 | Request execution timeout 98 | 99 | eval $lua_expression, $tuple_args, $opts, $cb->($result) 100 | Execute eval request 101 | 102 | $lua_expression 103 | Lua code that will be run in tarantool 104 | 105 | tuple_args 106 | Tuple (ARRAYREF) that will be passed as argument in lua code 107 | 108 | $opts 109 | HASHREF of additional options to the request 110 | 111 | timeout => $timeout 112 | Request execution timeout 113 | 114 | space => $space 115 | This space definition will be used to decode response tuple 116 | 117 | in => $in 118 | Format for parsing input (string). One char is for one argument 119 | ('s' = string, 'n' = number, 'a' = array, '*' = anything (type 120 | is determined automatically)) 121 | 122 | call $function_name, $tuple_args, $opts, $cb->($result) 123 | Execute eval request 124 | 125 | $function_name 126 | Lua function that will be called in tarantool 127 | 128 | tuple_args 129 | Tuple (ARRAYREF) that will be passed as argument in lua code 130 | 131 | $opts 132 | HASHREF of additional options to the request 133 | 134 | timeout => $timeout 135 | Request execution timeout 136 | 137 | space => $space 138 | This space definition will be used to decode response tuple 139 | 140 | in => $in 141 | Format for parsing input (string). One char is for one argument 142 | ('s' = string, 'n' = number, 'a' = array, '*' = anything (type 143 | is determined automatically)) 144 | 145 | select $space_name, $keys, $opts, $cb->($result) 146 | Execute select request 147 | 148 | $space_name 149 | Tarantool space name. 150 | 151 | $keys 152 | Select keys (ARRAYREF or HASHREF). 153 | 154 | $opts 155 | HASHREF of additional options to the request 156 | 157 | timeout => $timeout 158 | Request execution timeout 159 | 160 | hash => $hash 161 | Use hash as result 162 | 163 | index => $index 164 | Index name or id to use 165 | 166 | limit => $limit 167 | Select limit 168 | 169 | offset => $offset 170 | Select offset 171 | 172 | iterator => $iterator 173 | Select iterator type. It is recommended to use the predefined 174 | constants EV::Tarantool16::INDEX_#iterator_name# (eg. 175 | EV::Tarantool16::INDEX_EQ, EV::Tarantool16::INDEX_GE and so on). 176 | 177 | List of possible iterator names: 178 | 179 | * EQ 180 | 181 | * REQ 182 | 183 | * ALL 184 | 185 | * LT 186 | 187 | * LE 188 | 189 | * GE 190 | 191 | * GT 192 | 193 | * BITS_ALL_SET 194 | 195 | * BITS_ANY_SET 196 | 197 | * BITS_ALL_NOT_SET 198 | 199 | * OVERLAPS 200 | 201 | * NEIGHBOR 202 | 203 | You can also use string names as the value to this option (like 204 | 'EQ' or 'LT'). 205 | 206 | in => $in 207 | Format for parsing input (string). One char is for one argument 208 | ('s' = string, 'n' = number, 'a' = array, '*' = anything (type 209 | is determined automatically)) 210 | 211 | insert $space_name, $tuple, $opts, $cb->($result) 212 | Execute insert request 213 | 214 | $space_name 215 | Tarantool space name. 216 | 217 | $tuple 218 | Tuple to be inserted (ARRAYREF or HASHREF). 219 | 220 | $opts 221 | HASHREF of additional options to the request 222 | 223 | timeout => $timeout 224 | Request execution timeout 225 | 226 | hash => $hash 227 | Use hash as result 228 | 229 | replace => $replace 230 | Insert(0) or replace(1) a tuple 231 | 232 | in => $in 233 | Format for parsing input (string). One char is for one argument 234 | ('s' = string, 'n' = number, 'a' = array, '*' = anything (type 235 | is determined automatically)) 236 | 237 | replace $space_name, $tuple, $opts, $cb->($result) 238 | Execute replace request (same as insert, but replaces tuple if already 239 | exists). ($opts->{replace} = 1) 240 | 241 | update $space_name, $key, $operations, $opts, $cb->($result) 242 | Execute update request 243 | 244 | $space_name 245 | Tarantool space name. 246 | 247 | $key 248 | Select key where to perform update (ARRAYREF or HASHREF). 249 | 250 | $operations 251 | Update operations (ARRAYREF) in this format: [$field_no => 252 | $operation, $operation_args] Please refer to Tarantool 1.6 253 | documentaion for more details. 254 | 255 | $opts 256 | HASHREF of additional options to the request 257 | 258 | timeout => $timeout 259 | Request execution timeout 260 | 261 | hash => $hash 262 | Use hash as result 263 | 264 | index => $index 265 | Index name or id to use 266 | 267 | in => $in 268 | Format for parsing input (string). One char is for one argument 269 | ('s' = string, 'n' = number, 'a' = array, '*' = anything (type 270 | is determined automatically)) 271 | 272 | upsert $space_name, $tuple, $operations, $opts, $cb->($result) 273 | Execute upsert request 274 | 275 | $space_name 276 | Tarantool space name. 277 | 278 | $tuple 279 | A tuple that will be inserted to Tarantool if there is no tuple like 280 | it already (ARRAYREF or HASHREF). 281 | 282 | $operations 283 | Update operations (ARRAYREF) in this format: [$field_no => 284 | $operation, $operation_args] Please refer to Tarantool 1.6 285 | documentaion for more details. 286 | 287 | $opts 288 | HASHREF of additional options to the request 289 | 290 | timeout => $timeout 291 | Request execution timeout 292 | 293 | hash => $hash 294 | Use hash as result 295 | 296 | in => $in 297 | Format for parsing input (string). One char is for one argument 298 | ('s' = string, 'n' = number, 'a' = array, '*' = anything (type 299 | is determined automatically)) 300 | 301 | delete $space_name, $key, $opts, $cb->($result) 302 | Execute delete request 303 | 304 | $space_name 305 | Tarantool space name. 306 | 307 | $key 308 | Select key (ARRAYREF or HASHREF). 309 | 310 | $opts 311 | HASHREF of additional options to the request 312 | 313 | timeout => $timeout 314 | Request execution timeout 315 | 316 | hash => $hash 317 | Use hash as result 318 | 319 | index => $index 320 | Index name or id to use 321 | 322 | in => $in 323 | Format for parsing input (string). One char is for one argument 324 | ('s' = string, 'n' = number, 'a' = array, '*' = anything (type 325 | is determined automatically)) 326 | 327 | lua $function_name, $args, $opts, $cb->($result) 328 | Execute call request (added for backward compatibility with 329 | EV::Tarantool). See 'call' method. 330 | 331 | stats $cb->($result) 332 | Get Tarantool stats 333 | 334 | Result 335 | Returns a HASHREF, consisting of the following data: 336 | 337 | arena 338 | 339 | size 340 | Arena allocated size 341 | 342 | used 343 | Arena used size 344 | 345 | slabs 346 | Slabs memory use 347 | 348 | info 349 | 350 | lsn Tarantool log sequence number 351 | 352 | lut Last update time (current_time - box_info.replication.idle) 353 | 354 | lag Replication lag 355 | 356 | pid Process pid 357 | 358 | uptime 359 | Server's uptime 360 | 361 | op Total operations count for each operation (select, insert, ...) 362 | 363 | space 364 | Tuples count in each space 365 | 366 | RESULT 367 | Success result 368 | count => 1, 369 | tuples => [ 370 | { 371 | _t1 => 'tt1', 372 | _t2 => 'tt2', 373 | _t3 => 456, 374 | _t4 => 5 375 | } 376 | ], 377 | status => 'ok', 378 | code => 0, 379 | sync => 5 380 | 381 | count 382 | Tuples count 383 | 384 | tuples 385 | Tuples themeselves 386 | 387 | status 388 | Status string ('ok') 389 | 390 | code 391 | Return code (0 if ok, else error code 392 | ()) 394 | 395 | sync 396 | Request id 397 | 398 | Error result 399 | [undef, $error_msg, { 400 | errstr => $error_msg, 401 | status => 'error', 402 | code => $error_code, 403 | sync => 3 404 | }] 405 | 406 | errstr 407 | Error string 408 | 409 | status 410 | Status string ('error') 411 | 412 | code 413 | Return code (0 if ok, else error code 414 | ()) 416 | 417 | sync 418 | Request id 419 | 420 | AUTHOR 421 | igorcoding, , Mons Anderson, 422 | 423 | BUGS 424 | Please report any bugs or feature requests in 425 | 426 | 427 | COPYRIGHT AND LICENSE 428 | Copyright (C) 2015 by igorcoding 429 | 430 | This program is released under the following license: GPL 431 | 432 | -------------------------------------------------------------------------------- /README.pod: -------------------------------------------------------------------------------- 1 | =begin HTML 2 | 3 | =head4 Build Status 4 | 5 | 6 | 7 | 8 | 9 | 10 |
masterTravis CI Build status (master)
11 | 12 | =end HTML 13 | 14 | =head1 NAME 15 | 16 | EV::Tarantool16 - EV client for Tarantool 1.6 17 | 18 | =head1 VESRION 19 | 20 | Version 1.39 21 | 22 | 23 | =cut 24 | 25 | =head1 SYNOPSIS 26 | 27 | use EV::Tarantool16; 28 | my $c; $c = EV::Tarantool16->new({ 29 | host => '127.0.0.1', 30 | port => 3301, 31 | username => 'test_user', 32 | password => 'test_passwd', 33 | reconnect => 0.2, 34 | connected => sub { 35 | warn "connected: @_"; 36 | EV::unloop; 37 | }, 38 | connfail => sub { 39 | warn "connfail: @_ / $!"; 40 | EV::unloop; 41 | }, 42 | disconnected => sub { 43 | warn "discon: @_ / $!"; 44 | EV::unloop; 45 | }, 46 | }); 47 | 48 | $c->connect; 49 | EV::loop; 50 | 51 | $c->ping(sub { 52 | my $a = @_[0]; 53 | diag Dumper \@_ if !$a; 54 | EV::unloop; 55 | }); 56 | EV::loop; 57 | 58 | 59 | =head1 SUBROUTINES/METHODS 60 | 61 | =head2 new {option => value,...} 62 | 63 | Create new EV::Tarantool16 instance. 64 | 65 | =over 4 66 | 67 | =item host => $address 68 | 69 | Address connect to. 70 | 71 | =item port => $port 72 | 73 | Port connect to. 74 | 75 | =item username => $username 76 | 77 | Username. 78 | 79 | =item password => $password 80 | 81 | Password. 82 | 83 | =item reconnect => $reconnect 84 | 85 | Reconnect timeout. 86 | 87 | =item log_level => $log_level 88 | 89 | Logging level. Values: (0: None), (1: Error), (2: Warning), (3: Info), (4: Debug) 90 | 91 | =item cnntrace => $cnntrace 92 | 93 | Enable (1) or disable(0) evcnn tracing. 94 | 95 | =item ares_reuse => $ares_reuse 96 | 97 | Enable (1) or disable(0) c-ares connection reuse (default = 0). 98 | 99 | =item wbuf_limit => $wbuf_limit 100 | 101 | Write vector buffer length limit. Defaults to 16384. Set wbuf_limit = 0 to disable write buffer length check on every request. 102 | 103 | =item connected => $sub 104 | 105 | Called when connection to Tarantool 1.6 instance is established, authenticated successfully and retrieved spaces information from it. 106 | 107 | =item connfail => $sub 108 | 109 | Called when connection to Tarantool 1.6 instance failed. 110 | 111 | =item disconnected => $sub 112 | 113 | Called when Tarantool 1.6 instance disconnected. 114 | 115 | =back 116 | 117 | 118 | 119 | =cut 120 | 121 | =head2 connect 122 | 123 | Connect to Tarantool 1.6 instance. EV::Tarantool16->connected is called when connection is established. 124 | 125 | 126 | =cut 127 | 128 | =head2 disconnect 129 | 130 | Disconnect from Tarantool 1.6 instance. EV::Tarantool16->disconnected is called afterwards. 131 | 132 | 133 | =cut 134 | 135 | =head2 ping $opts, $cb->($result) 136 | 137 | Execute ping request 138 | 139 | =over 4 140 | 141 | =item $opts 142 | 143 | HASHREF of additional options to the request 144 | 145 | =over 4 146 | 147 | =item timeout => $timeout 148 | 149 | Request execution timeout 150 | 151 | =back 152 | 153 | =back 154 | 155 | 156 | =cut 157 | 158 | =head2 eval $lua_expression, $tuple_args, $opts, $cb->($result) 159 | 160 | Execute eval request 161 | 162 | =over 4 163 | 164 | =item $lua_expression 165 | 166 | Lua code that will be run in tarantool 167 | 168 | =item tuple_args 169 | 170 | Tuple (ARRAYREF) that will be passed as argument in lua code 171 | 172 | =item $opts 173 | 174 | HASHREF of additional options to the request 175 | 176 | =over 4 177 | 178 | =item timeout => $timeout 179 | 180 | Request execution timeout 181 | 182 | =item space => $space 183 | 184 | This space definition will be used to decode response tuple 185 | 186 | =item in => $in 187 | 188 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 189 | 190 | =back 191 | 192 | =back 193 | 194 | 195 | =cut 196 | 197 | =head2 call $function_name, $tuple_args, $opts, $cb->($result) 198 | 199 | Execute eval request 200 | 201 | =over 4 202 | 203 | =item $function_name 204 | 205 | Lua function that will be called in tarantool 206 | 207 | =item tuple_args 208 | 209 | Tuple (ARRAYREF) that will be passed as argument in lua code 210 | 211 | =item $opts 212 | 213 | HASHREF of additional options to the request 214 | 215 | =over 4 216 | 217 | =item timeout => $timeout 218 | 219 | Request execution timeout 220 | 221 | =item space => $space 222 | 223 | This space definition will be used to decode response tuple 224 | 225 | =item in => $in 226 | 227 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 228 | 229 | =back 230 | 231 | =back 232 | 233 | 234 | =cut 235 | 236 | =head2 select $space_name, $keys, $opts, $cb->($result) 237 | 238 | Execute select request 239 | 240 | =over 4 241 | 242 | =item $space_name 243 | 244 | Tarantool space name. 245 | 246 | =item $keys 247 | 248 | Select keys (ARRAYREF or HASHREF). 249 | 250 | =item $opts 251 | 252 | HASHREF of additional options to the request 253 | 254 | =over 4 255 | 256 | =item timeout => $timeout 257 | 258 | Request execution timeout 259 | 260 | =item hash => $hash 261 | 262 | Use hash as result 263 | 264 | =item index => $index 265 | 266 | Index name or id to use 267 | 268 | =item limit => $limit 269 | 270 | Select limit 271 | 272 | =item offset => $offset 273 | 274 | Select offset 275 | 276 | =item iterator => $iterator 277 | 278 | Select iterator type. It is recommended to use the predefined constants EV::Tarantool16::INDEX_#iterator_name# 279 | (eg. EV::Tarantool16::INDEX_EQ, EV::Tarantool16::INDEX_GE and so on). 280 | 281 | List of possible iterator names: 282 | 283 | =over 284 | 285 | =item * EQ 286 | 287 | =item * REQ 288 | 289 | =item * ALL 290 | 291 | =item * LT 292 | 293 | =item * LE 294 | 295 | =item * GE 296 | 297 | =item * GT 298 | 299 | =item * BITS_ALL_SET 300 | 301 | =item * BITS_ANY_SET 302 | 303 | =item * BITS_ALL_NOT_SET 304 | 305 | =item * OVERLAPS 306 | 307 | =item * NEIGHBOR 308 | 309 | =back 310 | 311 | You can also use string names as the value to this option (like 'EQ' or 'LT'). 312 | 313 | 314 | =item in => $in 315 | 316 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 317 | 318 | =back 319 | 320 | =back 321 | 322 | 323 | =cut 324 | 325 | =head2 insert $space_name, $tuple, $opts, $cb->($result) 326 | 327 | Execute insert request 328 | 329 | =over 4 330 | 331 | =item $space_name 332 | 333 | Tarantool space name. 334 | 335 | =item $tuple 336 | 337 | Tuple to be inserted (ARRAYREF or HASHREF). 338 | 339 | =item $opts 340 | 341 | HASHREF of additional options to the request 342 | 343 | =over 4 344 | 345 | =item timeout => $timeout 346 | 347 | Request execution timeout 348 | 349 | =item hash => $hash 350 | 351 | Use hash as result 352 | 353 | =item replace => $replace 354 | 355 | Insert(0) or replace(1) a tuple 356 | 357 | =item in => $in 358 | 359 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 360 | 361 | =back 362 | 363 | =back 364 | 365 | 366 | =cut 367 | 368 | =head2 replace $space_name, $tuple, $opts, $cb->($result) 369 | 370 | Execute replace request (same as insert, but replaces tuple if already exists). ($opts->{replace} = 1) 371 | 372 | 373 | =cut 374 | 375 | =head2 update $space_name, $key, $operations, $opts, $cb->($result) 376 | 377 | Execute update request 378 | 379 | =over 4 380 | 381 | =item $space_name 382 | 383 | Tarantool space name. 384 | 385 | =item $key 386 | 387 | Select key where to perform update (ARRAYREF or HASHREF). 388 | 389 | =item $operations 390 | 391 | Update operations (ARRAYREF) in this format: 392 | [$field_no => $operation, $operation_args] 393 | Please refer to Tarantool 1.6 documentaion for more details. 394 | 395 | =item $opts 396 | 397 | HASHREF of additional options to the request 398 | 399 | =over 4 400 | 401 | =item timeout => $timeout 402 | 403 | Request execution timeout 404 | 405 | =item hash => $hash 406 | 407 | Use hash as result 408 | 409 | =item index => $index 410 | 411 | Index name or id to use 412 | 413 | =item in => $in 414 | 415 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 416 | 417 | =back 418 | 419 | =back 420 | 421 | 422 | =cut 423 | 424 | =head2 upsert $space_name, $tuple, $operations, $opts, $cb->($result) 425 | 426 | Execute upsert request 427 | 428 | =over 4 429 | 430 | =item $space_name 431 | 432 | Tarantool space name. 433 | 434 | =item $tuple 435 | 436 | A tuple that will be inserted to Tarantool if there is no tuple like it already (ARRAYREF or HASHREF). 437 | 438 | =item $operations 439 | 440 | Update operations (ARRAYREF) in this format: 441 | [$field_no => $operation, $operation_args] 442 | Please refer to Tarantool 1.6 documentaion for more details. 443 | 444 | =item $opts 445 | 446 | HASHREF of additional options to the request 447 | 448 | =over 4 449 | 450 | =item timeout => $timeout 451 | 452 | Request execution timeout 453 | 454 | =item hash => $hash 455 | 456 | Use hash as result 457 | 458 | =item in => $in 459 | 460 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 461 | 462 | =back 463 | 464 | =back 465 | 466 | 467 | =cut 468 | 469 | =head2 delete $space_name, $key, $opts, $cb->($result) 470 | 471 | Execute delete request 472 | 473 | =over 4 474 | 475 | =item $space_name 476 | 477 | Tarantool space name. 478 | 479 | =item $key 480 | 481 | Select key (ARRAYREF or HASHREF). 482 | 483 | =item $opts 484 | 485 | HASHREF of additional options to the request 486 | 487 | =over 4 488 | 489 | =item timeout => $timeout 490 | 491 | Request execution timeout 492 | 493 | =item hash => $hash 494 | 495 | Use hash as result 496 | 497 | =item index => $index 498 | 499 | Index name or id to use 500 | 501 | =item in => $in 502 | 503 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 504 | 505 | =back 506 | 507 | =back 508 | 509 | 510 | =cut 511 | 512 | =head2 lua $function_name, $args, $opts, $cb->($result) 513 | 514 | Execute call request (added for backward compatibility with EV::Tarantool). See 'call' method. 515 | 516 | 517 | =cut 518 | 519 | =head2 stats $cb->($result) 520 | 521 | Get Tarantool stats 522 | 523 | =head3 Result 524 | 525 | Returns a HASHREF, consisting of the following data: 526 | 527 | =over 4 528 | 529 | =item arena 530 | 531 | =over 4 532 | 533 | =item size 534 | 535 | Arena allocated size 536 | 537 | =item used 538 | 539 | Arena used size 540 | 541 | =item slabs 542 | 543 | Slabs memory use 544 | 545 | =back 546 | 547 | =item info 548 | 549 | =over 4 550 | 551 | =item lsn 552 | 553 | Tarantool log sequence number 554 | 555 | =item lut 556 | 557 | Last update time (current_time - box_info.replication.idle) 558 | 559 | =item lag 560 | 561 | Replication lag 562 | 563 | =item pid 564 | 565 | Process pid 566 | 567 | =item uptime 568 | 569 | Server's uptime 570 | 571 | =back 572 | 573 | =item op 574 | 575 | Total operations count for each operation (select, insert, ...) 576 | 577 | =item space 578 | 579 | Tuples count in each space 580 | 581 | =back 582 | 583 | 584 | =cut 585 | 586 | =head1 RESULT 587 | 588 | =head2 Success result 589 | 590 | count => 1, 591 | tuples => [ 592 | { 593 | _t1 => 'tt1', 594 | _t2 => 'tt2', 595 | _t3 => 456, 596 | _t4 => 5 597 | } 598 | ], 599 | status => 'ok', 600 | code => 0, 601 | sync => 5 602 | 603 | =over 4 604 | 605 | =item count 606 | 607 | Tuples count 608 | 609 | =item tuples 610 | 611 | Tuples themeselves 612 | 613 | =item status 614 | 615 | Status string ('ok') 616 | 617 | =item code 618 | 619 | Return code (0 if ok, else error code (L)) 620 | 621 | =item sync 622 | 623 | Request id 624 | 625 | =back 626 | 627 | 628 | =cut 629 | 630 | =head2 Error result 631 | 632 | [undef, $error_msg, { 633 | errstr => $error_msg, 634 | status => 'error', 635 | code => $error_code, 636 | sync => 3 637 | }] 638 | 639 | =over 4 640 | 641 | =item errstr 642 | 643 | Error string 644 | 645 | =item status 646 | 647 | Status string ('error') 648 | 649 | =item code 650 | 651 | Return code (0 if ok, else error code (L)) 652 | 653 | =item sync 654 | 655 | Request id 656 | 657 | =back 658 | 659 | 660 | =cut 661 | 662 | =head1 AUTHOR 663 | 664 | igorcoding, Eigorcoding@gmail.comE, 665 | Mons Anderson, Emons@cpan.orgE 666 | 667 | =head1 BUGS 668 | 669 | Please report any bugs or feature requests in L 670 | 671 | =head1 COPYRIGHT AND LICENSE 672 | 673 | Copyright (C) 2015 by igorcoding 674 | 675 | This program is released under the following license: GPL 676 | 677 | 678 | =cut 679 | 680 | -------------------------------------------------------------------------------- /Tarantool16.xs: -------------------------------------------------------------------------------- 1 | #define XSEV_CON_HOOKS 1 2 | 3 | #include "EXTERN.h" 4 | #include "perl.h" 5 | #include "XSUB.h" 6 | #include "ppport.h" 7 | #include "EVAPI.h" 8 | 9 | #include "log.h" 10 | #include "xsevcnn.h" 11 | #include "xstnt16.h" 12 | 13 | #if __GNUC__ >= 3 14 | # define INLINE static inline 15 | #else 16 | # define INLINE static 17 | #endif 18 | 19 | #define TNT_CROAK(msg) STMT_START { \ 20 | croak("[Tarantool Error] %s", msg); \ 21 | } STMT_END 22 | 23 | #define PE_CROAK(msg) STMT_START { \ 24 | croak("[Programming Error] %s", msg); \ 25 | } STMT_END 26 | 27 | #ifndef TNT_WBUF_LIMIT 28 | # define TNT_WBUF_LIMIT 16384 29 | #endif 30 | 31 | typedef struct { 32 | xs_ev_cnn_struct; 33 | 34 | c_cb_discon_t on_disconnect_before; 35 | c_cb_discon_t on_disconnect_after; 36 | c_cb_conn_t on_connect_before; 37 | c_cb_conn_t on_connect_after; 38 | 39 | c_cb_conn_t default_on_connected_cb; 40 | struct sockaddr peer_info; 41 | 42 | uint32_t pending; 43 | uint32_t seq; 44 | U32 use_hash; 45 | HV *reqs; 46 | HV *spaces; 47 | SV *username; 48 | SV *password; 49 | uint8_t log_level; 50 | uint32_t wbuf_limit; 51 | } TntCnn; 52 | 53 | static const uint32_t _SPACE_SPACEID = 280; 54 | static const uint32_t _INDEX_SPACEID = 288; 55 | 56 | static const uint32_t _VSPACE_SPACEID = 281; 57 | static const uint32_t _VINDEX_SPACEID = 289; 58 | 59 | // static const char *_SPACE_SELECTOR = "return unpack(box.space._space:select{})"; 60 | // static const char *_INDEX_SELECTOR = "return unpack(box.space._index:select{})"; 61 | static const size_t SELECTOR_STR_LENGTH = 40; 62 | 63 | void tnt_on_connected_cb(ev_cnn *cnn, struct sockaddr *peer) { 64 | TntCnn *self = (TntCnn *) cnn; 65 | if (likely(peer != NULL)) { 66 | self->peer_info = *peer; 67 | } 68 | self->spaces = newHV(); 69 | } 70 | 71 | INLINE void call_connected(TntCnn *self) { 72 | self->default_on_connected_cb(&self->cnn, &self->peer_info); 73 | } 74 | 75 | INLINE void force_disconnect(TntCnn *self, const char *reason) { 76 | 77 | on_connect_reset(&self->cnn, 0, reason); 78 | } 79 | 80 | static void on_request_timer(EV_P_ ev_timer *t, int flags) { 81 | TntCtx *ctx = (TntCtx *) t; 82 | TntCnn *self = (TntCnn *) ctx->self; 83 | log_warn(self->log_level, "timer called on %p: %s", ctx, ctx->call); 84 | ENTER;SAVETMPS; 85 | dSP; 86 | 87 | (void) hv_delete( self->reqs, (char *) &ctx->id, sizeof(ctx->id),0); 88 | 89 | // ev_timer_stop(self->cnn.loop, &ctx->t); 90 | // do_disable_rw_timer(&self->cnn); 91 | SvREFCNT_dec(ctx->wbuf); 92 | if (ctx->f.size && !ctx->f.nofree) { 93 | safefree(ctx->f.f); 94 | } 95 | 96 | if (ctx->cb) { 97 | SPAGAIN; 98 | ENTER; SAVETMPS; 99 | 100 | PUSHMARK(SP); 101 | EXTEND(SP, 2); 102 | PUSHs( &PL_sv_undef ); 103 | PUSHs( sv_2mortal(newSVpvf("Request timed out")) ); 104 | PUTBACK; 105 | 106 | (void) call_sv( ctx->cb, G_DISCARD | G_VOID ); 107 | 108 | //SPAGAIN;PUTBACK; 109 | 110 | SvREFCNT_dec(ctx->cb); 111 | 112 | FREETMPS; LEAVE; 113 | } 114 | 115 | --self->pending; 116 | 117 | FREETMPS;LEAVE; 118 | } 119 | 120 | #define TIMEOUT_TIMER(self, ctx, iid, timeout) STMT_START { \ 121 | if (timeout > 0) { \ 122 | ev_timer_init(&ctx->t, on_request_timer, timeout, 0.); \ 123 | ev_timer_start(self->cnn.loop, &ctx->t); \ 124 | } \ 125 | } STMT_END 126 | 127 | #define INIT_TIMEOUT_TIMER(self, ctx, iid, opts) STMT_START { \ 128 | double timeout; \ 129 | SV **key; \ 130 | \ 131 | if ( opts && (key = hv_fetchs( opts, "timeout", 0 ))) { \ 132 | timeout = SvNV( *key ); \ 133 | /*cwarn("timeout set: %f",timeout);*/\ 134 | } else { \ 135 | timeout = self->cnn.rw_timeout; \ 136 | } \ 137 | TIMEOUT_TIMER(self, ctx, iid, timeout); \ 138 | } STMT_END 139 | 140 | #define __EXEC_REQUEST(self, ctxsv, ctx, iid, _cb) STMT_START { \ 141 | SvREFCNT_inc(ctx->cb = (_cb)); \ 142 | (void) hv_store( self->reqs, (char *)&iid, sizeof(iid), SvREFCNT_inc(ctxsv), 0 ); \ 143 | ++self->pending; \ 144 | do_write(&self->cnn,SvPVX(ctx->wbuf), SvCUR(ctx->wbuf)); \ 145 | } STMT_END 146 | 147 | #define EXEC_REQUEST_TIMEOUT(self, ctxsv, ctx, iid, pkt, opts, _cb) STMT_START { \ 148 | if ((ctx->wbuf = pkt)) { \ 149 | __EXEC_REQUEST(self, ctxsv, ctx, iid, _cb); \ 150 | INIT_TIMEOUT_TIMER(self, ctx, iid, opts); \ 151 | } \ 152 | } STMT_END 153 | 154 | #define EXEC_REQUEST(self, ctxsv, ctx, iid, pkt, _cb) STMT_START { \ 155 | if ((ctx->wbuf = pkt)) { \ 156 | __EXEC_REQUEST(self, ctxsv, ctx, iid, _cb); \ 157 | } \ 158 | } STMT_END 159 | 160 | #define INIT_CTX(_self, ctx, method, iid) STMT_START { \ 161 | ctx->self = _self; \ 162 | ctx->call = method; \ 163 | ctx->use_hash = _self->use_hash; \ 164 | ctx->log_level = _self->log_level; \ 165 | iid = ++_self->seq; \ 166 | ctx->id = iid; \ 167 | } STMT_END 168 | 169 | #define croak_cb_xsundef(cb, ...) STMT_START { \ 170 | _croak_cb(cb, __VA_ARGS__); \ 171 | XSRETURN_UNDEF; \ 172 | return; \ 173 | } STMT_END 174 | 175 | #define GET_OPTS(OPTS_NAME, opts_sv, cb) STMT_START { \ 176 | SV *_opts_sv = (opts_sv); \ 177 | OPTS_NAME = NULL; \ 178 | if (_opts_sv != NULL) { \ 179 | if (likely(SvROK(_opts_sv) && SvTYPE(SvRV(_opts_sv)) == SVt_PVHV)) { \ 180 | OPTS_NAME = (HV *) SvRV(_opts_sv); \ 181 | } else if (_opts_sv != &PL_sv_undef) { \ 182 | croak_cb_xsundef(cb, "Opts must be a HASHREF"); \ 183 | } \ 184 | } \ 185 | } STMT_END 186 | 187 | INLINE void _execute_eval(TntCnn *self, const char *expr) { 188 | dSVX(ctxsv, ctx, TntCtx); 189 | sv_2mortal(ctxsv); 190 | uint32_t iid; 191 | 192 | INIT_CTX(self, ctx, "eval", iid); 193 | SV *pkt = pkt_eval(ctx, iid, self->spaces, sv_2mortal(newSVpvn(expr, SELECTOR_STR_LENGTH)), sv_2mortal(newRV_noinc((SV *) newAV())), NULL, NULL); 194 | EXEC_REQUEST(self, ctxsv, ctx, iid, pkt, NULL); 195 | 196 | TIMEOUT_TIMER(self, ctx, iid, self->cnn.rw_timeout); 197 | } 198 | 199 | INLINE void _execute_select(TntCnn *self, uint32_t space_id) { 200 | dSVX(ctxsv, ctx, TntCtx); 201 | sv_2mortal(ctxsv); 202 | uint32_t iid; 203 | 204 | INIT_CTX(self, ctx, "select", iid); 205 | SV *pkt = pkt_select(ctx, iid, self->spaces, sv_2mortal(newSVuv(space_id)), sv_2mortal(newRV_noinc((SV *) newAV())), NULL, NULL); 206 | EXEC_REQUEST(self, ctxsv, ctx, iid, pkt, NULL); 207 | 208 | TIMEOUT_TIMER(self, ctx, iid, self->cnn.rw_timeout); 209 | } 210 | 211 | 212 | static void on_read(ev_cnn *self, size_t len) { 213 | ENTER; 214 | SAVETMPS; 215 | 216 | do_disable_rw_timer(self); 217 | 218 | TntCnn *tnt = (TntCnn *) self; 219 | char *rbuf = self->rbuf; 220 | char *end = rbuf + self->ruse; 221 | 222 | dSP; 223 | 224 | while ( rbuf < end ) { 225 | /* len */ 226 | ptrdiff_t buf_len = end - rbuf; 227 | if (buf_len < 5) { 228 | //cwarn("buf_len < 5"); 229 | debug("not enough"); 230 | break; 231 | } 232 | 233 | uint32_t pkt_length; 234 | decode_pkt_len_(&rbuf, pkt_length); 235 | 236 | if (buf_len - 5 < pkt_length) { 237 | //cwarn("not enough for a packet"); 238 | debug("not enough"); 239 | break; 240 | } 241 | rbuf += 5; 242 | 243 | HV *hv = (HV *) sv_2mortal((SV *) newHV()); 244 | 245 | /* header */ 246 | tnt_header_t hdr; 247 | int hdr_length = parse_reply_hdr(hv, rbuf, buf_len, &hdr, tnt->log_level); 248 | if (unlikely(hdr_length < 0)) { 249 | TNT_CROAK("Unexpected response header"); 250 | return; 251 | } 252 | if (unlikely(hdr.id <= 0)) { 253 | PE_CROAK("Wrong sync id (id <= 0)"); 254 | return; 255 | } 256 | 257 | TntCtx *ctx; 258 | SV *key = hv_delete(tnt->reqs, (char *) &hdr.id, sizeof(hdr.id), 0); 259 | 260 | if (!key) { 261 | rbuf += pkt_length; 262 | log_debug(tnt->log_level, "key %d not found", hdr.id); 263 | } else { 264 | rbuf += hdr_length; 265 | 266 | ctx = (TntCtx *) SvPVX(key); 267 | ev_timer_stop(self->loop, &ctx->t); 268 | SvREFCNT_dec(ctx->wbuf); 269 | if (ctx->f.size && !ctx->f.nofree) { 270 | safefree(ctx->f.f); 271 | } 272 | 273 | /* body */ 274 | 275 | AV *fields = (ctx->space && ctx->use_hash) ? ctx->space->fields : NULL; 276 | int body_length = parse_reply_body(ctx, hv, rbuf, buf_len, &ctx->f, fields); 277 | if (unlikely(body_length <= 0)) { 278 | rbuf += (pkt_length - hdr_length); 279 | log_error(tnt->log_level, "Unexpected response body. length = %d", body_length); 280 | } else { 281 | rbuf += body_length; 282 | } 283 | 284 | if (ctx->cb) { 285 | SPAGAIN; 286 | 287 | ENTER; SAVETMPS; 288 | 289 | SV **var = NULL; 290 | if (hdr.code == 0) { 291 | PUSHMARK(SP); 292 | EXTEND(SP, 1); 293 | PUSHs( sv_2mortal(newRV_noinc( SvREFCNT_inc_NN((SV *) hv) )) ); 294 | PUTBACK; 295 | } 296 | else { 297 | var = hv_fetchs(hv,"errstr",0); 298 | PUSHMARK(SP); 299 | EXTEND(SP, 3); 300 | PUSHs( &PL_sv_undef ); 301 | PUSHs( var && *var ? sv_2mortal(newSVsv(*var)) : &PL_sv_undef ); 302 | PUSHs( sv_2mortal(newRV_noinc( SvREFCNT_inc_NN((SV *) hv) )) ); 303 | PUTBACK; 304 | } 305 | 306 | (void) call_sv(ctx->cb, G_DISCARD | G_VOID); 307 | 308 | //SPAGAIN;PUTBACK; 309 | 310 | SvREFCNT_dec(ctx->cb); 311 | 312 | FREETMPS; LEAVE; 313 | } 314 | 315 | 316 | --tnt->pending; 317 | 318 | if (rbuf == end) { 319 | self->ruse = 0; 320 | if (tnt->pending == 0) { 321 | //do_disable_rw_timer(self); 322 | } 323 | else { 324 | //do_enable_rw_timer(self); 325 | } 326 | break; 327 | } 328 | } 329 | 330 | } 331 | 332 | self->ruse = end - rbuf; 333 | if (self->ruse > 0) { 334 | memmove(self->rbuf,rbuf,self->ruse); 335 | } 336 | 337 | FREETMPS; 338 | LEAVE; 339 | } 340 | 341 | static void on_index_info_read(ev_cnn *self, size_t len) { 342 | ENTER; 343 | SAVETMPS; 344 | 345 | do_disable_rw_timer(self); 346 | 347 | TntCnn *tnt = (TntCnn *) self; 348 | char *rbuf = self->rbuf; 349 | char *end = rbuf + self->ruse; 350 | 351 | while ( rbuf < end ) { 352 | /* len */ 353 | ptrdiff_t buf_len = end - rbuf; 354 | if (buf_len < 5) { 355 | //cwarn("buf_len < 5"); 356 | debug("not enough"); 357 | break; 358 | } 359 | 360 | uint32_t pkt_length; 361 | decode_pkt_len_(&rbuf, pkt_length); 362 | 363 | if (buf_len - 5 < pkt_length) { 364 | //cwarn("not enough for a packet"); 365 | debug("not enough"); 366 | break; 367 | } 368 | rbuf += 5; 369 | 370 | HV *hv = (HV *) sv_2mortal((SV *) newHV()); 371 | 372 | /* header */ 373 | tnt_header_t hdr; 374 | int hdr_length = parse_reply_hdr(hv, rbuf, buf_len, &hdr, tnt->log_level); 375 | if (unlikely(hdr_length < 0)) { 376 | TNT_CROAK("Unexpected response header"); 377 | return; 378 | } 379 | if (unlikely(hdr.id <= 0)) { 380 | PE_CROAK("Wrong sync id (id <= 0)"); 381 | return; 382 | } 383 | 384 | TntCtx *ctx; 385 | SV *key = hv_delete(tnt->reqs, (char *) &hdr.id, sizeof(hdr.id), 0); 386 | 387 | if (!key) { 388 | rbuf += pkt_length; 389 | log_debug(tnt->log_level, "key %d not found", hdr.id); 390 | } else { 391 | rbuf += hdr_length; 392 | 393 | ctx = (TntCtx *) SvPVX(key); 394 | ev_timer_stop(self->loop, &ctx->t); 395 | SvREFCNT_dec(ctx->wbuf); 396 | if (ctx->f.size && !ctx->f.nofree) { 397 | safefree(ctx->f.f); 398 | } 399 | 400 | 401 | int body_length = parse_index_body(tnt->spaces, hv, rbuf, buf_len, tnt->log_level); 402 | if (unlikely(body_length <= 0)) { 403 | rbuf += (pkt_length - hdr_length); 404 | log_error(tnt->log_level, "Unexpected response body. length = %d", body_length); 405 | force_disconnect(tnt, "Couldn\'t retrieve index info."); 406 | } else { 407 | rbuf += body_length; 408 | 409 | if (unlikely(hdr.code != 0)) { 410 | // log_error(tnt->log_level, "Failed to retrieve index info. Code = %d", (int) hdr.code); 411 | // force_disconnect(tnt, "Couldn\'t retrieve index info."); 412 | 413 | SV **var = hv_fetchs(hv,"errstr",0); 414 | log_error( 415 | tnt->log_level, 416 | "Couldn\'t retrieve indexes info. Code = %d, Message = \"%.*s\"", 417 | (int) hdr.code, 418 | (int) SvCUR(*var), 419 | SvPV_nolen(*var) 420 | ); 421 | 422 | SV *msg = sv_2mortal(newSVpvf( 423 | "Couldn\'t retrieve indexes info: %.*s", (int) SvCUR(*var), SvPV_nolen(*var) 424 | )); 425 | force_disconnect(tnt, SvPVX(msg)); 426 | } else { 427 | self->on_read = (c_cb_read_t) on_read; 428 | call_connected(tnt); 429 | } 430 | } 431 | 432 | 433 | --tnt->pending; 434 | 435 | if (rbuf == end) { 436 | self->ruse = 0; 437 | if (tnt->pending == 0) { 438 | //do_disable_rw_timer(self); 439 | } 440 | else { 441 | //do_enable_rw_timer(self); 442 | } 443 | break; 444 | } 445 | } 446 | 447 | } 448 | 449 | self->ruse = end - rbuf; 450 | if (self->ruse > 0) { 451 | memmove(self->rbuf,rbuf,self->ruse); 452 | } 453 | 454 | FREETMPS; 455 | LEAVE; 456 | } 457 | 458 | static void on_spaces_info_read(ev_cnn *self, size_t len) { 459 | ENTER; 460 | SAVETMPS; 461 | 462 | do_disable_rw_timer(self); 463 | 464 | TntCnn *tnt = (TntCnn *) self; 465 | char *rbuf = self->rbuf; 466 | char *end = rbuf + self->ruse; 467 | 468 | while ( rbuf < end ) { 469 | /* len */ 470 | ptrdiff_t buf_len = end - rbuf; 471 | if (buf_len < 5) { 472 | //cwarn("buf_len < 5"); 473 | debug("not enough"); 474 | break; 475 | } 476 | 477 | uint32_t pkt_length; 478 | decode_pkt_len_(&rbuf, pkt_length); 479 | 480 | if (buf_len - 5 < pkt_length) { 481 | //cwarn("not enough for a packet"); 482 | debug("not enough"); 483 | break; 484 | } 485 | rbuf += 5; 486 | 487 | HV *hv = (HV *) sv_2mortal((SV *) newHV()); 488 | 489 | /* header */ 490 | tnt_header_t hdr; 491 | int hdr_length = parse_reply_hdr(hv, rbuf, buf_len, &hdr, tnt->log_level); 492 | if (unlikely(hdr_length < 0)) { 493 | TNT_CROAK("Unexpected response header"); 494 | return; 495 | } 496 | if (unlikely(hdr.id <= 0)) { 497 | PE_CROAK("Wrong sync id (id <= 0)"); 498 | return; 499 | } 500 | 501 | TntCtx *ctx; 502 | SV *key = hv_delete(tnt->reqs, (char *) &hdr.id, sizeof(hdr.id), 0); 503 | 504 | if (!key) { 505 | rbuf += pkt_length; 506 | log_debug(tnt->log_level, "key %d not found", hdr.id); 507 | } else { 508 | rbuf += hdr_length; 509 | 510 | ctx = (TntCtx *) SvPVX(key); 511 | ev_timer_stop(self->loop, &ctx->t); 512 | SvREFCNT_dec(ctx->wbuf); 513 | if (ctx->f.size && !ctx->f.nofree) { 514 | safefree(ctx->f.f); 515 | } 516 | 517 | int body_length = parse_spaces_body(hv, rbuf, buf_len, tnt->log_level); 518 | 519 | if (unlikely(body_length <= 0)) { 520 | rbuf += (pkt_length - hdr_length); 521 | log_error(tnt->log_level, "Unexpected response body. length = %d", body_length); 522 | force_disconnect(tnt, "Couldn\'t retrieve space info (body_length <= 0)."); 523 | } else { 524 | rbuf += body_length; 525 | 526 | SV **var = NULL; 527 | if (unlikely(hdr.code != 0)) { 528 | // log_error(tnt->log_level, "Couldn\'t retrieve space info. Code = %d", (int) hdr.code); 529 | // force_disconnect(tnt, "Couldn\'t retrieve space info"); 530 | var = hv_fetchs(hv,"errstr",0); 531 | log_error( 532 | tnt->log_level, 533 | "Couldn\'t retrieve spaces info. Code = %d, Message = \"%.*s\"", 534 | (int) hdr.code, 535 | (int) SvCUR(*var), 536 | SvPV_nolen(*var) 537 | ); 538 | 539 | SV *msg = sv_2mortal(newSVpvf( 540 | "Couldn\'t retrieve spaces info: %.*s", (int) SvCUR(*var), SvPV_nolen(*var) 541 | )); 542 | force_disconnect(tnt, SvPVX(msg)); 543 | } else { 544 | if ((var = hv_fetchs(hv, "data", 0)) && SvOK(*var) && SvROK(*var)) { 545 | if (tnt->spaces) { 546 | destroy_spaces(tnt->spaces); 547 | } 548 | tnt->spaces = (HV *) SvREFCNT_inc(SvRV(*var)); 549 | 550 | self->on_read = (c_cb_read_t) on_index_info_read; 551 | // _execute_eval(tnt, _INDEX_SELECTOR); 552 | _execute_select(tnt, _VINDEX_SPACEID); 553 | // self->on_read = (c_cb_read_t) on_read; 554 | } else { 555 | log_error(tnt->log_level, "Couldn\'t retrieve space info. No data parsed"); 556 | force_disconnect(tnt, "Couldn\'t retrieve space info (no parsed data)."); 557 | } 558 | } 559 | } 560 | 561 | --tnt->pending; 562 | 563 | if (rbuf == end) { 564 | self->ruse = 0; 565 | if (tnt->pending == 0) { 566 | //do_disable_rw_timer(self); 567 | } 568 | else { 569 | //do_enable_rw_timer(self); 570 | } 571 | break; 572 | } 573 | } 574 | 575 | } 576 | 577 | self->ruse = end - rbuf; 578 | if (self->ruse > 0) { 579 | memmove(self->rbuf,rbuf,self->ruse); 580 | } 581 | 582 | FREETMPS; 583 | LEAVE; 584 | } 585 | 586 | static void on_auth_read(ev_cnn *self, size_t len) { 587 | ENTER; 588 | SAVETMPS; 589 | 590 | do_disable_rw_timer(self); 591 | 592 | TntCnn *tnt = (TntCnn *) self; 593 | char *rbuf = self->rbuf; 594 | char *end = rbuf + self->ruse; 595 | 596 | while ( rbuf < end ) { 597 | /* len */ 598 | ptrdiff_t buf_len = end - rbuf; 599 | if (buf_len < 5) { 600 | //cwarn("buf_len < 5"); 601 | debug("not enough"); 602 | break; 603 | } 604 | 605 | uint32_t pkt_length; 606 | decode_pkt_len_(&rbuf, pkt_length); 607 | 608 | if (buf_len - 5 < pkt_length) { 609 | //cwarn("not enough for a packet"); 610 | debug("not enough"); 611 | break; 612 | } 613 | rbuf += 5; 614 | 615 | HV *hv = (HV *) sv_2mortal((SV *) newHV()); 616 | 617 | /* header */ 618 | tnt_header_t hdr; 619 | int hdr_length = parse_reply_hdr(hv, rbuf, buf_len, &hdr, tnt->log_level); 620 | if (unlikely(hdr_length < 0)) { 621 | TNT_CROAK("Unexpected response header"); 622 | return; 623 | } 624 | if (unlikely(hdr.id <= 0)) { 625 | PE_CROAK("Wrong sync id (id <= 0)"); 626 | return; 627 | } 628 | 629 | TntCtx *ctx; 630 | SV *key = hv_delete(tnt->reqs, (char *) &hdr.id, sizeof(hdr.id), 0); 631 | 632 | if (!key) { 633 | rbuf += pkt_length; 634 | log_debug(tnt->log_level, "key %d not found", hdr.id); 635 | } else { 636 | rbuf += hdr_length; 637 | 638 | ctx = (TntCtx *) SvPVX(key); 639 | ev_timer_stop(self->loop, &ctx->t); 640 | SvREFCNT_dec(ctx->wbuf); 641 | if (ctx->f.size && !ctx->f.nofree) { 642 | safefree(ctx->f.f); 643 | } 644 | 645 | /* body */ 646 | 647 | AV *fields = (ctx->space && ctx->use_hash) ? ctx->space->fields : NULL; 648 | int body_length = parse_reply_body(ctx, hv, rbuf, buf_len, &ctx->f, fields); 649 | if (unlikely(body_length <= 0)) { 650 | rbuf += (pkt_length - hdr_length); 651 | log_error(tnt->log_level, "Unexpected response body. length = %d", body_length); 652 | force_disconnect(tnt, "Couldn\'t authenticate (body_length <= 0)."); 653 | } else { 654 | rbuf += body_length; 655 | 656 | SV **var = NULL; 657 | if (hdr.code == 0) { 658 | self->on_read = (c_cb_read_t) on_spaces_info_read; 659 | // _execute_eval(tnt, _SPACE_SELECTOR); 660 | _execute_select(tnt, _VSPACE_SPACEID); 661 | // self->on_read = (c_cb_read_t) on_read; 662 | } 663 | else { 664 | var = hv_fetchs(hv,"errstr",0); 665 | force_disconnect(tnt, SvPVX(*var)); 666 | } 667 | } 668 | 669 | --tnt->pending; 670 | 671 | if (rbuf == end) { 672 | self->ruse = 0; 673 | if (tnt->pending == 0) { 674 | //do_disable_rw_timer(self); 675 | } 676 | else { 677 | //do_enable_rw_timer(self); 678 | } 679 | break; 680 | } 681 | } 682 | 683 | } 684 | 685 | self->ruse = end - rbuf; 686 | if (self->ruse > 0) { 687 | memmove(self->rbuf,rbuf,self->ruse); 688 | } 689 | 690 | FREETMPS; 691 | LEAVE; 692 | } 693 | 694 | static void on_greet_read(ev_cnn *self, size_t len) { 695 | 696 | ENTER; 697 | SAVETMPS; 698 | 699 | do_disable_rw_timer(self); 700 | 701 | TntCnn *tnt = (TntCnn *) self; 702 | char *rbuf = self->rbuf; 703 | char *end = rbuf + self->ruse; 704 | 705 | ptrdiff_t buf_len = end - rbuf; 706 | if (buf_len < 128) { 707 | return; 708 | } 709 | 710 | char *tnt_ver_begin = NULL, *tnt_ver_end = NULL; 711 | char *salt_begin = NULL, *salt_end = NULL; 712 | decode_greeting(rbuf, tnt_ver_begin, tnt_ver_end, salt_begin, salt_end); 713 | log_info(tnt->log_level, "%.*s", (int) (tnt_ver_end - tnt_ver_begin), tnt_ver_begin); 714 | 715 | self->ruse -= buf_len; 716 | if (self->ruse > 0) { 717 | //cwarn("move buf on %zu",self->ruse); 718 | memmove(self->rbuf,rbuf,self->ruse); 719 | } 720 | 721 | if (tnt->username && SvOK(tnt->username) && SvPOK(tnt->username) && tnt->password && SvOK(tnt->password) && SvPOK(tnt->password)) { 722 | dSVX(ctxsv, ctx, TntCtx); 723 | sv_2mortal(ctxsv); 724 | uint32_t iid; 725 | INIT_CTX(tnt, ctx, "auth", iid); 726 | SV *pkt = pkt_authenticate(iid, tnt->username, tnt->password, salt_begin, salt_end, NULL); 727 | 728 | self->on_read = (c_cb_read_t) on_auth_read; 729 | EXEC_REQUEST(tnt, ctxsv, ctx, iid, pkt, NULL); 730 | TIMEOUT_TIMER(tnt, ctx, iid, tnt->cnn.rw_timeout); 731 | } else { 732 | self->on_read = (c_cb_read_t) on_spaces_info_read; 733 | // _execute_eval(tnt, _SPACE_SELECTOR); 734 | _execute_select(tnt, _VSPACE_SPACEID); 735 | // self->on_read = (c_cb_read_t) on_read; 736 | // call_connected(tnt); 737 | } 738 | 739 | FREETMPS; 740 | LEAVE; 741 | } 742 | 743 | void free_reqs (TntCnn *self, const char *message) { 744 | if (unlikely(!self->reqs)) return; 745 | 746 | ENTER;SAVETMPS; 747 | 748 | dSP; 749 | 750 | HE *ent; 751 | (void) hv_iterinit( self->reqs ); 752 | while ((ent = hv_iternext( self->reqs ))) { 753 | TntCtx *ctx = (TntCtx *) SvPVX( HeVAL(ent) ); 754 | ev_timer_stop(self->cnn.loop,&ctx->t); 755 | SvREFCNT_dec(ctx->wbuf); 756 | if (ctx->f.size && !ctx->f.nofree) { 757 | safefree(ctx->f.f); 758 | } 759 | 760 | if (ctx->cb) { 761 | SPAGAIN; 762 | ENTER; SAVETMPS; 763 | 764 | PUSHMARK(SP); 765 | EXTEND(SP, 2); 766 | PUSHs( &PL_sv_undef ); 767 | PUSHs( sv_2mortal(newSVpvf("%s", message)) ); 768 | PUTBACK; 769 | 770 | (void) call_sv( ctx->cb, G_DISCARD | G_VOID ); 771 | 772 | //SPAGAIN;PUTBACK; 773 | 774 | SvREFCNT_dec(ctx->cb); 775 | 776 | FREETMPS; LEAVE; 777 | } 778 | 779 | --self->pending; 780 | } 781 | 782 | hv_clear(self->reqs); 783 | 784 | FREETMPS;LEAVE; 785 | } 786 | 787 | 788 | static void on_disconnect (TntCnn *self, int err, const char *reason) { 789 | ENTER;SAVETMPS; 790 | 791 | if (err == 0) { 792 | free_reqs(self, "Connection closed"); 793 | } else { 794 | SV *msg = sv_2mortal(newSVpvf("Disconnected: %s",strerror(err))); 795 | free_reqs(self, SvPVX(msg)); 796 | } 797 | 798 | if (self->spaces) { 799 | destroy_spaces(self->spaces); 800 | self->spaces = NULL; 801 | } 802 | 803 | self->cnn.on_read = (c_cb_read_t) on_greet_read; 804 | 805 | FREETMPS;LEAVE; 806 | } 807 | 808 | INLINE SV *get_bool(const char *name) { 809 | SV *sv = get_sv(name, 1); 810 | 811 | SvREADONLY_on(sv); 812 | SvREADONLY_on(SvRV(sv)); 813 | 814 | return sv; 815 | } 816 | 817 | 818 | MODULE = EV::Tarantool16 PACKAGE = EV::Tarantool16 819 | PROTOTYPES: DISABLE 820 | BOOT: 821 | { 822 | I_EV_API ("EV::Tarantool16"); 823 | I_EV_CNN_API("EV::Tarantool16"); 824 | 825 | types_boolean_stash = gv_stashpv("Types::Serialiser::Boolean", 1); 826 | 827 | types_true = get_bool("Types::Serialiser::true"); 828 | types_false = get_bool("Types::Serialiser::false"); 829 | } 830 | 831 | 832 | void new(SV *pk, HV *conf) 833 | PPCODE: 834 | if (0) pk = pk; 835 | xs_ev_cnn_new(TntCnn); // declares YourType *self, set ST(0) 836 | self->default_on_connected_cb = self->cnn.on_connected; 837 | self->cnn.on_connected = (c_cb_conn_t) tnt_on_connected_cb; 838 | self->on_disconnect_before = (c_cb_discon_t) on_disconnect; 839 | self->cnn.on_read = (c_cb_read_t) on_greet_read; 840 | 841 | self->reqs = newHV(); 842 | self->use_hash = 1; 843 | self->spaces = NULL; 844 | self->spaces = NULL; 845 | 846 | SV **key; 847 | if ((key = hv_fetchs(conf, "hash", 0)) ) self->use_hash = SvOK(*key) ? SvIV(*key) : 0; 848 | if ((key = hv_fetchs(conf, "username", 0)) && SvPOK(*key)) SvREFCNT_inc(self->username = *key); 849 | if ((key = hv_fetchs(conf, "password", 0)) && SvPOK(*key)) SvREFCNT_inc(self->password = *key); 850 | if ((key = hv_fetchs(conf, "log_level", 0)) && (SvOK(*key) && SvIOK(*key))) { 851 | self->log_level = SvIV(*key); 852 | } else { 853 | self->log_level = _LOG_INFO; 854 | } 855 | if ((key = hv_fetchs(conf, "wbuf_limit", 0))) { 856 | if (SvOK(*key)) { 857 | IV wbuf_limit = SvIV(*key); 858 | self->wbuf_limit = wbuf_limit > 0 ? wbuf_limit : 0; 859 | } else { 860 | self->wbuf_limit = TNT_WBUF_LIMIT; 861 | } 862 | } 863 | 864 | XSRETURN(1); 865 | 866 | 867 | void DESTROY(SV *this) 868 | PPCODE: 869 | if (0) this = this; 870 | xs_ev_cnn_self(TntCnn); 871 | 872 | if (!PL_dirty) { 873 | if (self->reqs) { 874 | free_reqs(self, "Destroyed"); 875 | SvREFCNT_dec(self->reqs); 876 | self->reqs = NULL; 877 | } 878 | if (self->spaces) { 879 | destroy_spaces(self->spaces); 880 | self->spaces = NULL; 881 | } 882 | } 883 | if (self->username) SvREFCNT_dec(self->username); 884 | if (self->password) SvREFCNT_dec(self->password); 885 | xs_ev_cnn_destroy(self); 886 | 887 | 888 | void reqs(SV *this) 889 | PPCODE: 890 | if (0) this = this; 891 | xs_ev_cnn_self(TntCnn); 892 | ST(0) = sv_2mortal(newRV_inc((SV *)self->reqs)); 893 | XSRETURN(1); 894 | 895 | 896 | void spaces(SV *this) 897 | PPCODE: 898 | if (0) this = this; 899 | xs_ev_cnn_self(TntCnn); 900 | ST(0) = sv_2mortal(newRV_inc((SV *)self->spaces)); 901 | XSRETURN(1); 902 | 903 | void sync(SV *this) 904 | PPCODE: 905 | if (0) this = this; 906 | xs_ev_cnn_self(TntCnn); 907 | ST(0) = sv_2mortal(newSViv(self->seq)); 908 | XSRETURN(1); 909 | 910 | 911 | void ping(SV *this, ... ) 912 | PPCODE: 913 | if (0) this = this; 914 | xs_ev_cnn_self(TntCnn); 915 | SV *cb = ST(items-1); 916 | xs_ev_cnn_checkconn_wlimit(self, cb, self->wbuf_limit); 917 | 918 | HV *opts = NULL; 919 | GET_OPTS(opts, items == 3 ? ST( 1 ) : 0, cb); 920 | dSVX(ctxsv, ctx, TntCtx); 921 | sv_2mortal(ctxsv); 922 | uint32_t iid; 923 | INIT_CTX(self, ctx, "ping", iid); 924 | SV *pkt = pkt_ping(iid); 925 | EXEC_REQUEST_TIMEOUT(self, ctxsv, ctx, iid, pkt, opts, cb); 926 | 927 | XSRETURN_UNDEF; 928 | 929 | 930 | void select( SV *this, SV *space, SV *keys, ... ) 931 | PPCODE: 932 | if (0) this = this; 933 | // TODO: croak cleanup may be solved with refcnt+mortal 934 | xs_ev_cnn_self(TntCnn); 935 | SV *cb = ST(items-1); 936 | xs_ev_cnn_checkconn_wlimit(self, cb, self->wbuf_limit); 937 | 938 | HV *opts = NULL; 939 | GET_OPTS(opts, items == 5 ? ST( 3 ) : 0, cb); 940 | dSVX(ctxsv, ctx, TntCtx); 941 | sv_2mortal(ctxsv); 942 | uint32_t iid; 943 | INIT_CTX(self, ctx, "select", iid); 944 | SV *pkt = pkt_select(ctx, iid, self->spaces, space, keys, opts, cb ); 945 | EXEC_REQUEST_TIMEOUT(self, ctxsv, ctx, iid, pkt, opts, cb); 946 | 947 | XSRETURN_UNDEF; 948 | 949 | 950 | void insert( SV *this, SV *space, SV *t, ... ) 951 | PPCODE: 952 | if (0) this = this; 953 | xs_ev_cnn_self(TntCnn); 954 | SV *cb = ST(items-1); 955 | xs_ev_cnn_checkconn_wlimit(self, cb, self->wbuf_limit); 956 | 957 | HV *opts = NULL; 958 | GET_OPTS(opts, items == 5 ? ST( 3 ) : 0, cb); 959 | dSVX(ctxsv, ctx, TntCtx); 960 | sv_2mortal(ctxsv); 961 | uint32_t iid; 962 | INIT_CTX(self, ctx, "insert", iid); 963 | SV *pkt = pkt_insert(ctx, iid, self->spaces, space, t, opts, cb ); 964 | EXEC_REQUEST_TIMEOUT(self, ctxsv, ctx, iid, pkt, opts, cb); 965 | 966 | XSRETURN_UNDEF; 967 | 968 | void replace( SV *this, SV *space, SV *t, ... ) 969 | PPCODE: 970 | if (0) this = this; 971 | xs_ev_cnn_self(TntCnn); 972 | SV *cb = ST(items-1); 973 | xs_ev_cnn_checkconn_wlimit(self, cb, self->wbuf_limit); 974 | 975 | HV *opts = NULL; 976 | GET_OPTS(opts, items == 5 ? ST( 3 ) : 0, cb); 977 | dSVX(ctxsv, ctx, TntCtx); 978 | sv_2mortal(ctxsv); 979 | uint32_t iid; 980 | INIT_CTX(self, ctx, "replace", iid); 981 | (void) hv_stores(opts, "replace", newSVuv(1)); 982 | SV *pkt = pkt_insert(ctx, iid, self->spaces, space, t, opts, cb ); 983 | EXEC_REQUEST_TIMEOUT(self, ctxsv, ctx, iid, pkt, opts, cb); 984 | 985 | XSRETURN_UNDEF; 986 | 987 | 988 | void update( SV *this, SV *space, SV *key, SV *operations, ... ) 989 | PPCODE: 990 | if (0) this = this; 991 | xs_ev_cnn_self(TntCnn); 992 | SV *cb = ST(items-1); 993 | xs_ev_cnn_checkconn_wlimit(self, cb, self->wbuf_limit); 994 | 995 | HV *opts = NULL; 996 | GET_OPTS(opts, items == 6 ? ST( 4 ) : 0, cb); 997 | dSVX(ctxsv, ctx, TntCtx); 998 | sv_2mortal(ctxsv); 999 | uint32_t iid; 1000 | INIT_CTX(self, ctx, "update", iid); 1001 | SV *pkt = pkt_update(ctx, iid, self->spaces, space, key, operations, opts, cb ); 1002 | EXEC_REQUEST_TIMEOUT(self, ctxsv, ctx, iid, pkt, opts, cb); 1003 | 1004 | XSRETURN_UNDEF; 1005 | 1006 | 1007 | void upsert( SV *this, SV *space, SV *tuple, SV *operations, ... ) 1008 | PPCODE: 1009 | if (0) this = this; 1010 | xs_ev_cnn_self(TntCnn); 1011 | SV *cb = ST(items-1); 1012 | xs_ev_cnn_checkconn_wlimit(self, cb, self->wbuf_limit); 1013 | 1014 | HV *opts = NULL; 1015 | GET_OPTS(opts, items == 6 ? ST( 4 ) : 0, cb); 1016 | dSVX(ctxsv, ctx, TntCtx); 1017 | sv_2mortal(ctxsv); 1018 | uint32_t iid; 1019 | INIT_CTX(self, ctx, "upsert", iid); 1020 | SV *pkt = pkt_upsert(ctx, iid, self->spaces, space, tuple, operations, opts, cb ); 1021 | EXEC_REQUEST_TIMEOUT(self, ctxsv, ctx, iid, pkt, opts, cb); 1022 | 1023 | XSRETURN_UNDEF; 1024 | 1025 | 1026 | void delete( SV *this, SV *space, SV *t, ... ) 1027 | PPCODE: 1028 | if (0) this = this; 1029 | xs_ev_cnn_self(TntCnn); 1030 | SV *cb = ST(items-1); 1031 | xs_ev_cnn_checkconn_wlimit(self, cb, self->wbuf_limit); 1032 | 1033 | HV *opts = NULL; 1034 | GET_OPTS(opts, items == 5 ? ST( 3 ) : 0, cb); 1035 | dSVX(ctxsv, ctx, TntCtx); 1036 | sv_2mortal(ctxsv); 1037 | uint32_t iid; 1038 | INIT_CTX(self, ctx, "delete", iid); 1039 | SV *pkt = pkt_delete(ctx, iid, self->spaces, space, t, opts, cb ); 1040 | EXEC_REQUEST_TIMEOUT(self, ctxsv, ctx, iid, pkt, opts, cb); 1041 | 1042 | XSRETURN_UNDEF; 1043 | 1044 | 1045 | void eval( SV *this, SV *expression, SV *t, ... ) 1046 | PPCODE: 1047 | if (0) this = this; 1048 | // TODO: croak cleanup may be solved with refcnt+mortal 1049 | xs_ev_cnn_self(TntCnn); 1050 | SV *cb = ST(items-1); 1051 | xs_ev_cnn_checkconn_wlimit(self, cb, self->wbuf_limit); 1052 | 1053 | HV *opts = NULL; 1054 | GET_OPTS(opts, items == 5 ? ST( 3 ) : 0, cb); 1055 | dSVX(ctxsv, ctx, TntCtx); 1056 | sv_2mortal(ctxsv); 1057 | uint32_t iid; 1058 | INIT_CTX(self, ctx, "eval", iid); 1059 | SV *pkt = pkt_eval(ctx, iid, self->spaces, expression, t, opts, cb ); 1060 | EXEC_REQUEST_TIMEOUT(self, ctxsv, ctx, iid, pkt, opts, cb); 1061 | 1062 | XSRETURN_UNDEF; 1063 | 1064 | 1065 | void call( SV *this, SV *function_name, SV *t, ... ) 1066 | PPCODE: 1067 | if (0) this = this; 1068 | // TODO: croak cleanup may be solved with refcnt+mortal 1069 | xs_ev_cnn_self(TntCnn); 1070 | SV *cb = ST(items-1); 1071 | xs_ev_cnn_checkconn_wlimit(self, cb, self->wbuf_limit); 1072 | 1073 | HV *opts = NULL; 1074 | GET_OPTS(opts, items == 5 ? ST( 3 ) : 0, cb); 1075 | dSVX(ctxsv, ctx, TntCtx); 1076 | sv_2mortal(ctxsv); 1077 | uint32_t iid; 1078 | INIT_CTX(self, ctx, "call", iid); 1079 | SV *pkt = pkt_call(ctx, iid, self->spaces, function_name, t, opts, cb ); 1080 | EXEC_REQUEST_TIMEOUT(self, ctxsv, ctx, iid, pkt, opts, cb); 1081 | 1082 | XSRETURN_UNDEF; 1083 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | VAGRANTFILE_API_VERSION = "2" 5 | 6 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 7 | 8 | config.vm.box = "ubuntu/trusty64" 9 | 10 | config.vm.synced_folder "./", "/home/vagrant/EV-Tarantool16/" 11 | config.vm.synced_folder "./../../", "/home/vagrant/Projects/" 12 | 13 | config.vm.provision "shell", 14 | privileged: false, 15 | path: "provision/vagrant.sh" 16 | 17 | config.vm.provider "virtualbox" do |v| 18 | v.memory = 2000 19 | v.cpus = 4 20 | v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"] 21 | v.customize ["modifyvm", :id, "--natdnsproxy1", "on"] 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /bench/bench.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use 5.010; 3 | use FindBin; 4 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 5 | use EV; 6 | use AE; 7 | use EV::Tarantool16; 8 | use Time::HiRes 'sleep','time'; 9 | use Scalar::Util 'weaken'; 10 | use Errno; 11 | use Test::More; 12 | use Test::Deep; 13 | use Data::Dumper; 14 | use Getopt::Long; 15 | 16 | use Inserter; 17 | use Selector; 18 | 19 | sub main() { 20 | my $tnt = { 21 | port => 3301, 22 | host => '127.0.0.1' 23 | }; 24 | 25 | my $common_opts = { 26 | space => 'sophier', 27 | rps => 1, 28 | count => undef 29 | }; 30 | 31 | my $inserter_opts = { 32 | min_blob => 10, 33 | max_blob => 30, 34 | updater => 0 35 | }; 36 | 37 | my $selector_opts = { 38 | max_id => 50, 39 | percentiles => [50, 75, 90, 99, 100] 40 | }; 41 | 42 | my $mode; 43 | my @modes = ('inserter', 'selector', 'updater'); 44 | 45 | GetOptions ("mode=s" => \$mode, 46 | 47 | "space=s" => \$common_opts->{space}, 48 | "rps=f" => \$common_opts->{rps}, 49 | "count=i" => \$common_opts->{count}, 50 | 51 | "min_blob=f" => \$inserter_opts->{min_blob}, 52 | "max_blob=f" => \$inserter_opts->{max_blob}, 53 | 54 | "max_id=i" => \$selector_opts->{max_id}, 55 | ) 56 | or die("Error in command line arguments\n"); 57 | 58 | die("Mode not specified. Possible values: ", Dumper(\@modes)) unless $mode; 59 | if (not($mode ~~ @modes)) { 60 | die 'Unknown mode: ', $mode; 61 | } 62 | 63 | my $c; 64 | my $blob_gen_w; 65 | my $connected = 0; 66 | 67 | my $starttime = time; 68 | 69 | 70 | my $sig_w; $sig_w = AE::signal "INT", sub { 71 | if (defined($c)) { 72 | if ($connected == 1) { 73 | $c->disconnect(); 74 | } 75 | undef $c; 76 | } 77 | 78 | if (defined($blob_gen_w)) { 79 | $blob_gen_w = undef; 80 | } 81 | }; 82 | 83 | $c = EV::Tarantool16->new({ 84 | host => $tnt->{host}, 85 | port => $tnt->{port}, 86 | reconnect => 0.2, 87 | connected => sub { 88 | warn "connected: @_"; 89 | $connected = 1; 90 | 91 | 92 | my $t; $t = AE::timer 1.0, 0, sub { 93 | undef $t; 94 | EV::unloop; 95 | }; 96 | 97 | }, 98 | connfail => sub { 99 | my $err = 0+$!; 100 | is $err, Errno::ECONNREFUSED, 'connfail - refused' or diag "$!, $_[1]"; 101 | EV::unloop; 102 | }, 103 | disconnected => sub { 104 | warn "discon: @_ / $!"; 105 | EV::unloop; 106 | }, 107 | }); 108 | 109 | $c->connect; 110 | EV::loop; 111 | 112 | if ($mode eq 'inserter') { 113 | my %opts = (%$common_opts, %$inserter_opts); 114 | Inserter::executor($c, \%opts, sub { 115 | my ($generated_keys) = @_; 116 | # say Dumper $generated_keys; 117 | EV::unloop; 118 | }); 119 | } elsif ($mode eq 'selector') { 120 | my %opts = (%$common_opts, %$selector_opts); 121 | 122 | Selector::executor($c, \%opts, sub { 123 | my ($stats) = @_; 124 | say Dumper $stats; 125 | EV::unloop; 126 | }); 127 | } elsif ($mode eq 'updater') { 128 | $inserter_opts->{updater} = 1; 129 | my %opts = (%$common_opts, %$inserter_opts); 130 | 131 | Inserter::executor($c, \%opts, sub { 132 | my ($generated_keys) = @_; 133 | # say Dumper $generated_keys; 134 | EV::unloop; 135 | }); 136 | }else { 137 | EV::unloop; 138 | die('not implemented!'); 139 | } 140 | EV::loop; 141 | 142 | if (defined($c)) { 143 | $c->disconnect; 144 | undef $c; 145 | my $elapsed = time - $starttime; 146 | say 'Done. time: ', $elapsed; 147 | } 148 | } 149 | 150 | 151 | main(); 152 | -------------------------------------------------------------------------------- /bench/lib/Inserter.pm: -------------------------------------------------------------------------------- 1 | package Inserter; 2 | 3 | use strict; 4 | use 5.010; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use EV; 8 | use AE; 9 | use Data::Dumper; 10 | 11 | use Util; 12 | 13 | sub generate_key { 14 | my ($k, $total_count, $replace_mode) = @_; 15 | if (!$replace_mode) { 16 | ++$$k; 17 | } else { 18 | $$k = Util::rand_num 1, $total_count + 1; 19 | } 20 | return sprintf "%020d", $$k; 21 | } 22 | 23 | sub generate_blobs { 24 | my ($size_selector, $max_count, $rps, $min_size, $max_size, $updater_mode, $inserter, $keys, $done_cb) = @_; 25 | 26 | my $urandom = "/dev/urandom"; 27 | open(my $fh, "<", $urandom) or die "cannot open < $urandom: $!"; 28 | my $insert_period = 1.0 / $rps; 29 | 30 | my $replace; 31 | my $log_str; 32 | if (defined($updater_mode) and $updater_mode) { 33 | $replace = 1; 34 | $log_str = 'Updating'; 35 | } else { 36 | $replace = 0; 37 | $log_str = 'Generating'; 38 | } 39 | 40 | my $t; 41 | 42 | $size_selector->(sub { 43 | my $total_count = $_[0]; 44 | 45 | my $i = 1; 46 | my $k = 0; 47 | $t = AE::timer $insert_period, $insert_period, sub { 48 | if (defined($max_count) && $i > $max_count) { 49 | say "Finishing up. i = $i"; 50 | undef $t; 51 | $done_cb->(); 52 | return; 53 | } 54 | ++$i; 55 | my $key = generate_key \$k, $total_count, $replace; 56 | # printf "%s %s\n", $log_str, $key; 57 | my $blob_size = Util::rand_num $min_size, $max_size; 58 | my $blob; 59 | read $fh, $blob, $blob_size; 60 | 61 | $inserter->($key, $blob, $replace, sub { 62 | my ($resp) = @_; 63 | if (defined($resp)) { 64 | push @$keys, $key; 65 | } 66 | }); 67 | }; 68 | }); 69 | 70 | return $t; 71 | } 72 | 73 | sub tnt_inserter { 74 | my ($c, $space_name, $key, $blob, $replace, $cb) = @_; 75 | 76 | $c->insert($space_name, [$key, $blob], { replace => $replace }, sub { 77 | my $a = @_[0]; 78 | # say Dumper \@_ unless $a; 79 | $cb->($a) if $cb; 80 | }); 81 | } 82 | 83 | sub create_tnt_inserter { 84 | my ($c, $space_name) = @_; 85 | 86 | return sub { 87 | my ($key, $blob, $replace, $cb) = @_; 88 | return tnt_inserter $c, $space_name, $key, $blob, $replace, $cb; 89 | }; 90 | } 91 | 92 | 93 | 94 | sub tnt_size_selector { 95 | my ($c, $space_name, $cb) = @_; 96 | $c->eval("return {box.space.$space_name:len{}}", [], {}, sub { 97 | my $a = @_[0]; 98 | my $resp = $a->{tuples}->[0]; 99 | $cb->($resp->[0]) if $cb; 100 | }); 101 | } 102 | 103 | sub create_tnt_size_selector { 104 | my ($c, $space_name) = @_; 105 | 106 | return sub { 107 | my ($cb) = @_; 108 | return tnt_size_selector $c, $space_name, $cb; 109 | }; 110 | } 111 | 112 | sub executor { 113 | my ($c, $opts, $cb) = @_; 114 | 115 | my $generated_keys = []; 116 | 117 | my $selector = create_tnt_size_selector $c, $opts->{space}; 118 | my $inserter = create_tnt_inserter $c, $opts->{space}; 119 | generate_blobs $selector, $opts->{count}, $opts->{rps}, $opts->{min_blob}, $opts->{max_blob}, $opts->{updater}, $inserter, $generated_keys, sub { 120 | $cb->($generated_keys); 121 | }; 122 | } 123 | 124 | 1; 125 | -------------------------------------------------------------------------------- /bench/lib/Selector.pm: -------------------------------------------------------------------------------- 1 | package Selector; 2 | 3 | use strict; 4 | use 5.010; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use Time::HiRes 'sleep','time'; 8 | use EV; 9 | use AE; 10 | use Data::Dumper; 11 | use List::BinarySearch qw( binsearch binsearch_pos ); 12 | use Scalar::Util qw( weaken ); 13 | 14 | use Util; 15 | 16 | sub running_stats { 17 | my ($data, $exec_time) = @_; 18 | my $old_mean = $data->{mean}; 19 | my $old_count = $data->{count}; 20 | 21 | $data->{count} += 1; 22 | 23 | $data->{mean} *= $old_count; 24 | $data->{mean} += $exec_time; 25 | $data->{mean} /= $data->{count}; 26 | 27 | $data->{var} += $old_mean * $old_mean; 28 | $data->{var} *= $old_count; 29 | $data->{var} += $exec_time * $exec_time; 30 | $data->{var} /= $data->{count}; 31 | $data->{var} -= $data->{mean} * $data->{mean}; 32 | 33 | $data->{std} = sqrt($data->{var}); 34 | 35 | if (!defined($data->{min}) or $exec_time < $data->{min}) { 36 | $data->{min} = $exec_time; 37 | } 38 | 39 | if (!defined($data->{max}) or $exec_time > $data->{max}) { 40 | $data->{max} = $exec_time; 41 | } 42 | } 43 | 44 | sub evaluate_percentiles { 45 | my ($datapoints, $stats_data, $percentiles) = @_; 46 | my @sorted_data = sort {$a <=> $b} @$datapoints; 47 | my @percent_ranks = (); 48 | my $N = @sorted_data; 49 | for my $d (1..$N) { 50 | my $rank = 100 / $N * ($d - 0.5); 51 | push @percent_ranks, $rank; 52 | } 53 | 54 | $stats_data->{percentiles} = {}; 55 | 56 | for my $P (@$percentiles) { 57 | if ($P < $percent_ranks[0]) { 58 | $stats_data->{percentiles}->{$P} = $sorted_data[0]; 59 | } elsif ($P > $percent_ranks[$#percent_ranks]) { 60 | $stats_data->{percentiles}->{$P} = $sorted_data[$#percent_ranks]; 61 | } else { 62 | my $index = binsearch {$a <=> $b} $P, @percent_ranks; 63 | if (defined($index)) { 64 | $stats_data->{percentiles}->{$P} = $sorted_data[$index]; 65 | } else { 66 | $index = binsearch_pos { $a <=> $b } $P, @percent_ranks; 67 | my $k = $index - 1; 68 | my $k_1 = $index; 69 | my $interp = ($P - $percent_ranks[$k]) / ($percent_ranks[$k_1] - $percent_ranks[$k]); 70 | $stats_data->{percentiles}->{$P} = $sorted_data[$k] + $interp * ($sorted_data[$k_1] - $sorted_data[$k]); 71 | } 72 | } 73 | } 74 | } 75 | 76 | 77 | 78 | sub select_blobs { 79 | my ($count, $rps, $max_id, $selector, $datapoints, $stats_data, $done_cb) = @_; 80 | 81 | my $period = 1.0 / $rps; 82 | 83 | my $i = 1; 84 | my $measured_rps = 0; 85 | my $rps_measures = 0; 86 | my $last_id = $i; 87 | 88 | my $tt; $tt = AE::timer 1, 1, sub { 89 | $measured_rps += $i - $last_id; 90 | $last_id = $i; 91 | ++$rps_measures; 92 | }; 93 | 94 | my $done_called = 0; 95 | my $t; $t = sub { 96 | # my $t = $t or return; 97 | if (defined($count) && $i > $count) { 98 | undef $t; 99 | # if (not $done_called) { 100 | # $done_called = 1; 101 | undef $tt; 102 | $measured_rps /= $rps_measures if $rps_measures != 0; 103 | say Dumper $measured_rps; 104 | $done_cb->(); 105 | # } 106 | return; 107 | } 108 | my $id = Util::rand_num 1, $max_id; 109 | my $key = sprintf "%020d", $id; 110 | # printf "Selecting %s\n", $key; 111 | ++$i; 112 | 113 | my $begin_time = time; 114 | #printf "%.4f. started %d\n", EV::now, $i; 115 | 116 | $selector->($key, sub { 117 | if (defined $t) { 118 | my ($resp) = @_; 119 | my $exec_time = time - $begin_time; 120 | if (defined($resp)) { 121 | push @{$datapoints->{success}}, $exec_time; 122 | running_stats $stats_data->{success}, $exec_time; 123 | } else { 124 | push @{$datapoints->{error}}, $exec_time; 125 | running_stats $stats_data->{error}, $exec_time; 126 | } 127 | #printf "%.4f. finished %d\n", EV::now, $i; 128 | $t->(); 129 | } 130 | }); 131 | }; 132 | 133 | $t->() for 1..$rps; 134 | weaken($t); 135 | 136 | return $t; 137 | } 138 | 139 | sub tnt_selector { 140 | my ($c, $space_name, $key, $cb) = @_; 141 | 142 | $c->select($space_name, [$key], {}, sub { 143 | my $a = @_[0]; 144 | say Dumper \@_ unless $a; 145 | $cb->($a) if $cb; 146 | }); 147 | } 148 | 149 | sub create_tnt_selector { 150 | my ($c, $space_name) = @_; 151 | 152 | return sub { 153 | my ($key, $cb) = @_; 154 | return tnt_selector $c, $space_name, $key, $cb; 155 | }; 156 | } 157 | 158 | sub executor { 159 | my ($c, $opts, $cb) = @_; 160 | 161 | my $datapoints = { 162 | success => [], 163 | error => [] 164 | }; 165 | 166 | my $stats_data = { 167 | success => { 168 | mean => 0.0, 169 | var => 0.0, 170 | std => 0.0, 171 | min => undef, 172 | max => undef, 173 | count => 0 174 | }, 175 | error => { 176 | mean => 0.0, 177 | var => 0.0, 178 | std => 0.0, 179 | min => undef, 180 | max => undef, 181 | count => 0 182 | } 183 | }; 184 | 185 | my $selector = create_tnt_selector $c, $opts->{space}; 186 | select_blobs $opts->{count}, $opts->{rps}, $opts->{max_id}, $selector, $datapoints, $stats_data, sub { 187 | evaluate_percentiles $datapoints->{success}, $stats_data->{success}, $opts->{percentiles}; 188 | evaluate_percentiles $datapoints->{error}, $stats_data->{error}, $opts->{percentiles}; 189 | $cb->($stats_data); 190 | }; 191 | } 192 | 193 | 1; 194 | -------------------------------------------------------------------------------- /bench/lib/Util.pm: -------------------------------------------------------------------------------- 1 | package Util; 2 | 3 | use strict; 4 | use 5.010; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | 8 | sub rand_num { 9 | my ($min_size, $max_size) = @_; 10 | return $min_size + int(rand($max_size - $min_size)); 11 | } 12 | 13 | 1; 14 | -------------------------------------------------------------------------------- /bench/test.pl: -------------------------------------------------------------------------------- 1 | use 5.010; 2 | use strict; 3 | use AE; 4 | use EV; 5 | use Scalar::Util 'weaken'; 6 | use Time::HiRes; 7 | use Data::Dumper; 8 | 9 | my $count = 2; 10 | my $max = 20; 11 | 12 | my $action;$action = sub { 13 | my $cb = shift; 14 | my $w;$w = AE::timer rand()/1, 0, sub { 15 | undef $w; 16 | $cb->(); 17 | }; 18 | }; 19 | 20 | my $i = 0; 21 | my $avg_time = 0; 22 | my $total = 0; 23 | 24 | my $t;$t = sub { 25 | # my $t = $t or return; 26 | 27 | if ($i >= $max) { 28 | say 'hey'; 29 | undef $t; 30 | # EV::unloop; 31 | return; 32 | } 33 | ++$i; 34 | printf "%.4f. started %d\n", EV::now, $i; 35 | my $begin = time; 36 | $action->(sub { 37 | if (defined $t) { 38 | printf "%.4f. finished %d\n", EV::now, $i; 39 | ++$total; 40 | $avg_time += time - $begin; 41 | $t->(); 42 | } 43 | }); 44 | }; 45 | 46 | $t->() for 1..$count; 47 | weaken $t; 48 | 49 | EV::loop; 50 | say 'here'; 51 | 52 | $avg_time /= $total; 53 | say Dumper $avg_time; 54 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | make clean 4 | perl Makefile.PL && make 5 | -------------------------------------------------------------------------------- /lib/EV/Tarantool16.pm: -------------------------------------------------------------------------------- 1 | package EV::Tarantool16; 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | use Types::Serialiser; 7 | 8 | our $VERSION = '1.39'; 9 | 10 | use EV (); 11 | 12 | require XSLoader; 13 | XSLoader::load('EV::Tarantool16', $VERSION); 14 | 15 | use constant { 16 | INDEX_EQ => 0, 17 | INDEX_REQ => 1, 18 | INDEX_ALL => 2, 19 | INDEX_LT => 3, 20 | INDEX_LE => 4, 21 | INDEX_GE => 5, 22 | INDEX_GT => 6, 23 | INDEX_BITS_ALL_SET => 7, 24 | INDEX_BITS_ANY_SET => 8, 25 | INDEX_BITS_ALL_NOT_SET => 9, 26 | INDEX_OVERLAPS => 10, 27 | INDEX_NEIGHBOR => 11, 28 | }; 29 | 30 | =begin HTML 31 | 32 | =head4 Build Status 33 | 34 | 35 | 36 | 37 | 38 | 39 |
masterTravis CI Build status (master)
40 | 41 | =end HTML 42 | 43 | =head1 NAME 44 | 45 | EV::Tarantool16 - EV client for Tarantool 1.6 46 | 47 | =head1 VESRION 48 | 49 | Version 1.39 50 | 51 | =cut 52 | 53 | =head1 SYNOPSIS 54 | 55 | use EV::Tarantool16; 56 | my $c; $c = EV::Tarantool16->new({ 57 | host => '127.0.0.1', 58 | port => 3301, 59 | username => 'test_user', 60 | password => 'test_passwd', 61 | reconnect => 0.2, 62 | connected => sub { 63 | warn "connected: @_"; 64 | EV::unloop; 65 | }, 66 | connfail => sub { 67 | warn "connfail: @_ / $!"; 68 | EV::unloop; 69 | }, 70 | disconnected => sub { 71 | warn "discon: @_ / $!"; 72 | EV::unloop; 73 | }, 74 | }); 75 | 76 | $c->connect; 77 | EV::loop; 78 | 79 | $c->ping(sub { 80 | my $a = @_[0]; 81 | diag Dumper \@_ if !$a; 82 | EV::unloop; 83 | }); 84 | EV::loop; 85 | 86 | 87 | =head1 SUBROUTINES/METHODS 88 | 89 | =head2 new {option => value,...} 90 | 91 | Create new EV::Tarantool16 instance. 92 | 93 | =over 4 94 | 95 | =item host => $address 96 | 97 | Address connect to. 98 | 99 | =item port => $port 100 | 101 | Port connect to. 102 | 103 | =item username => $username 104 | 105 | Username. 106 | 107 | =item password => $password 108 | 109 | Password. 110 | 111 | =item reconnect => $reconnect 112 | 113 | Reconnect timeout. 114 | 115 | =item log_level => $log_level 116 | 117 | Logging level. Values: (0: None), (1: Error), (2: Warning), (3: Info), (4: Debug) 118 | 119 | =item cnntrace => $cnntrace 120 | 121 | Enable (1) or disable(0) evcnn tracing. 122 | 123 | =item ares_reuse => $ares_reuse 124 | 125 | Enable (1) or disable(0) c-ares connection reuse (default = 0). 126 | 127 | =item wbuf_limit => $wbuf_limit 128 | 129 | Write vector buffer length limit. Defaults to 16384. Set wbuf_limit = 0 to disable write buffer length check on every request. 130 | 131 | =item connected => $sub 132 | 133 | Called when connection to Tarantool 1.6 instance is established, authenticated successfully and retrieved spaces information from it. 134 | 135 | =item connfail => $sub 136 | 137 | Called when connection to Tarantool 1.6 instance failed. 138 | 139 | =item disconnected => $sub 140 | 141 | Called when Tarantool 1.6 instance disconnected. 142 | 143 | =back 144 | 145 | 146 | =cut 147 | 148 | =head2 connect 149 | 150 | Connect to Tarantool 1.6 instance. EV::Tarantool16->connected is called when connection is established. 151 | 152 | =cut 153 | 154 | =head2 disconnect 155 | 156 | Disconnect from Tarantool 1.6 instance. EV::Tarantool16->disconnected is called afterwards. 157 | 158 | =cut 159 | 160 | =head2 ping $opts, $cb->($result) 161 | 162 | Execute ping request 163 | 164 | =over 4 165 | 166 | =item $opts 167 | 168 | HASHREF of additional options to the request 169 | 170 | =over 4 171 | 172 | =item timeout => $timeout 173 | 174 | Request execution timeout 175 | 176 | =back 177 | 178 | =back 179 | 180 | =cut 181 | 182 | =head2 eval $lua_expression, $tuple_args, $opts, $cb->($result) 183 | 184 | Execute eval request 185 | 186 | =over 4 187 | 188 | =item $lua_expression 189 | 190 | Lua code that will be run in tarantool 191 | 192 | =item tuple_args 193 | 194 | Tuple (ARRAYREF) that will be passed as argument in lua code 195 | 196 | =item $opts 197 | 198 | HASHREF of additional options to the request 199 | 200 | =over 4 201 | 202 | =item timeout => $timeout 203 | 204 | Request execution timeout 205 | 206 | =item space => $space 207 | 208 | This space definition will be used to decode response tuple 209 | 210 | =item in => $in 211 | 212 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 213 | 214 | =back 215 | 216 | =back 217 | 218 | =cut 219 | 220 | =head2 call $function_name, $tuple_args, $opts, $cb->($result) 221 | 222 | Execute eval request 223 | 224 | =over 4 225 | 226 | =item $function_name 227 | 228 | Lua function that will be called in tarantool 229 | 230 | =item tuple_args 231 | 232 | Tuple (ARRAYREF) that will be passed as argument in lua code 233 | 234 | =item $opts 235 | 236 | HASHREF of additional options to the request 237 | 238 | =over 4 239 | 240 | =item timeout => $timeout 241 | 242 | Request execution timeout 243 | 244 | =item space => $space 245 | 246 | This space definition will be used to decode response tuple 247 | 248 | =item in => $in 249 | 250 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 251 | 252 | =back 253 | 254 | =back 255 | 256 | =cut 257 | 258 | =head2 select $space_name, $keys, $opts, $cb->($result) 259 | 260 | Execute select request 261 | 262 | =over 4 263 | 264 | =item $space_name 265 | 266 | Tarantool space name. 267 | 268 | =item $keys 269 | 270 | Select keys (ARRAYREF or HASHREF). 271 | 272 | =item $opts 273 | 274 | HASHREF of additional options to the request 275 | 276 | =over 4 277 | 278 | =item timeout => $timeout 279 | 280 | Request execution timeout 281 | 282 | =item hash => $hash 283 | 284 | Use hash as result 285 | 286 | =item index => $index 287 | 288 | Index name or id to use 289 | 290 | =item limit => $limit 291 | 292 | Select limit 293 | 294 | =item offset => $offset 295 | 296 | Select offset 297 | 298 | =item iterator => $iterator 299 | 300 | Select iterator type. It is recommended to use the predefined constants EV::Tarantool16::INDEX_#iterator_name# 301 | (eg. EV::Tarantool16::INDEX_EQ, EV::Tarantool16::INDEX_GE and so on). 302 | 303 | List of possible iterator names: 304 | 305 | =over 306 | 307 | =item * EQ 308 | 309 | =item * REQ 310 | 311 | =item * ALL 312 | 313 | =item * LT 314 | 315 | =item * LE 316 | 317 | =item * GE 318 | 319 | =item * GT 320 | 321 | =item * BITS_ALL_SET 322 | 323 | =item * BITS_ANY_SET 324 | 325 | =item * BITS_ALL_NOT_SET 326 | 327 | =item * OVERLAPS 328 | 329 | =item * NEIGHBOR 330 | 331 | =back 332 | 333 | You can also use string names as the value to this option (like 'EQ' or 'LT'). 334 | 335 | 336 | =item in => $in 337 | 338 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 339 | 340 | =back 341 | 342 | =back 343 | 344 | =cut 345 | 346 | =head2 insert $space_name, $tuple, $opts, $cb->($result) 347 | 348 | Execute insert request 349 | 350 | =over 4 351 | 352 | =item $space_name 353 | 354 | Tarantool space name. 355 | 356 | =item $tuple 357 | 358 | Tuple to be inserted (ARRAYREF or HASHREF). 359 | 360 | =item $opts 361 | 362 | HASHREF of additional options to the request 363 | 364 | =over 4 365 | 366 | =item timeout => $timeout 367 | 368 | Request execution timeout 369 | 370 | =item hash => $hash 371 | 372 | Use hash as result 373 | 374 | =item replace => $replace 375 | 376 | Insert(0) or replace(1) a tuple 377 | 378 | =item in => $in 379 | 380 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 381 | 382 | =back 383 | 384 | =back 385 | 386 | =cut 387 | 388 | =head2 replace $space_name, $tuple, $opts, $cb->($result) 389 | 390 | Execute replace request (same as insert, but replaces tuple if already exists). ($opts->{replace} = 1) 391 | 392 | =cut 393 | 394 | =head2 update $space_name, $key, $operations, $opts, $cb->($result) 395 | 396 | Execute update request 397 | 398 | =over 4 399 | 400 | =item $space_name 401 | 402 | Tarantool space name. 403 | 404 | =item $key 405 | 406 | Select key where to perform update (ARRAYREF or HASHREF). 407 | 408 | =item $operations 409 | 410 | Update operations (ARRAYREF) in this format: 411 | [$field_no => $operation, $operation_args] 412 | Please refer to Tarantool 1.6 documentaion for more details. 413 | 414 | =item $opts 415 | 416 | HASHREF of additional options to the request 417 | 418 | =over 4 419 | 420 | =item timeout => $timeout 421 | 422 | Request execution timeout 423 | 424 | =item hash => $hash 425 | 426 | Use hash as result 427 | 428 | =item index => $index 429 | 430 | Index name or id to use 431 | 432 | =item in => $in 433 | 434 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 435 | 436 | =back 437 | 438 | =back 439 | 440 | =cut 441 | 442 | =head2 upsert $space_name, $tuple, $operations, $opts, $cb->($result) 443 | 444 | Execute upsert request 445 | 446 | =over 4 447 | 448 | =item $space_name 449 | 450 | Tarantool space name. 451 | 452 | =item $tuple 453 | 454 | A tuple that will be inserted to Tarantool if there is no tuple like it already (ARRAYREF or HASHREF). 455 | 456 | =item $operations 457 | 458 | Update operations (ARRAYREF) in this format: 459 | [$field_no => $operation, $operation_args] 460 | Please refer to Tarantool 1.6 documentaion for more details. 461 | 462 | =item $opts 463 | 464 | HASHREF of additional options to the request 465 | 466 | =over 4 467 | 468 | =item timeout => $timeout 469 | 470 | Request execution timeout 471 | 472 | =item hash => $hash 473 | 474 | Use hash as result 475 | 476 | =item in => $in 477 | 478 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 479 | 480 | =back 481 | 482 | =back 483 | 484 | =cut 485 | 486 | =head2 delete $space_name, $key, $opts, $cb->($result) 487 | 488 | Execute delete request 489 | 490 | =over 4 491 | 492 | =item $space_name 493 | 494 | Tarantool space name. 495 | 496 | =item $key 497 | 498 | Select key (ARRAYREF or HASHREF). 499 | 500 | =item $opts 501 | 502 | HASHREF of additional options to the request 503 | 504 | =over 4 505 | 506 | =item timeout => $timeout 507 | 508 | Request execution timeout 509 | 510 | =item hash => $hash 511 | 512 | Use hash as result 513 | 514 | =item index => $index 515 | 516 | Index name or id to use 517 | 518 | =item in => $in 519 | 520 | Format for parsing input (string). One char is for one argument ('s' = string, 'n' = number, 'a' = array, '*' = anything (type is determined automatically)) 521 | 522 | =back 523 | 524 | =back 525 | 526 | =cut 527 | 528 | =head2 lua $function_name, $args, $opts, $cb->($result) 529 | 530 | Execute call request (added for backward compatibility with EV::Tarantool). See 'call' method. 531 | 532 | =cut 533 | 534 | *lua = \&call; 535 | 536 | =head2 stats $cb->($result) 537 | 538 | Get Tarantool stats 539 | 540 | =head3 Result 541 | 542 | Returns a HASHREF, consisting of the following data: 543 | 544 | =over 4 545 | 546 | =item arena 547 | 548 | =over 4 549 | 550 | =item size 551 | 552 | Arena allocated size 553 | 554 | =item used 555 | 556 | Arena used size 557 | 558 | =item slabs 559 | 560 | Slabs memory use 561 | 562 | =back 563 | 564 | =item info 565 | 566 | =over 4 567 | 568 | =item lsn 569 | 570 | Tarantool log sequence number 571 | 572 | =item lut 573 | 574 | Last update time (current_time - box_info.replication.idle) 575 | 576 | =item lag 577 | 578 | Replication lag 579 | 580 | =item pid 581 | 582 | Process pid 583 | 584 | =item uptime 585 | 586 | Server's uptime 587 | 588 | =back 589 | 590 | =item op 591 | 592 | Total operations count for each operation (select, insert, ...) 593 | 594 | =item space 595 | 596 | Tuples count in each space 597 | 598 | =back 599 | 600 | =cut 601 | sub stats { 602 | my $self = shift; 603 | my $cb = pop; 604 | my $opts = shift; 605 | my $expression = qq{ 606 | local fiber = require('fiber') 607 | local slab_info = box.slab.info() 608 | local stat = {} 609 | stat['arena'] = {} 610 | stat['arena']['size'] = slab_info.arena_size 611 | stat['arena']['used'] = slab_info.arena_used 612 | stat['arena']['slabs'] = 0 613 | for i,s in pairs(slab_info.slabs) do 614 | stat['arena']['slabs'] = stat['arena']['slabs'] + s.slab_count * s.slab_size 615 | end 616 | 617 | stat['arena']['free'] = slab_info.arena_size - slab_info.arena_used 618 | 619 | local box_info = box.info 620 | stat['info'] = {} 621 | stat['info']['lsn'] = box_info.server.lsn 622 | if (box_info.replication.status ~= 'off') then 623 | stat['info']['lut'] = fiber.time() - box_info.replication.idle 624 | stat['info']['lag'] = box_info.replication.lag 625 | end 626 | stat['info']['pid'] = box_info.pid 627 | stat['info']['uptime'] = box_info.uptime 628 | 629 | local ops = box.stat() 630 | stat['op'] = {} 631 | for op,op_info in pairs(ops) do 632 | stat['op'][string.lower(op)] = op_info.total 633 | end 634 | 635 | stat['space'] = {} 636 | for space_name,v in pairs(box.space) do 637 | if (not string.match(space_name, "[0-9]+") and v['engine'] ~= 'sysview') then 638 | stat['space'][space_name] = box.space[space_name]:len() 639 | end 640 | end 641 | 642 | return {stat} 643 | }; 644 | 645 | my $eval_cb = sub { 646 | if (!$_[0]) { 647 | my $error_msg = $_[1]; 648 | $cb->(undef, "Couldn\'t get stats. Error: $error_msg"); 649 | return; 650 | } 651 | my $stat = $_[0]->{tuples}->[0]->[0]; 652 | $cb->($stat); 653 | }; 654 | 655 | if ($opts) { 656 | $self->eval($expression, [], $opts, $eval_cb); 657 | } else { 658 | $self->eval($expression, [], $eval_cb); 659 | } 660 | } 661 | 662 | 663 | 664 | =head1 RESULT 665 | 666 | =head2 Success result 667 | 668 | count => 1, 669 | tuples => [ 670 | { 671 | _t1 => 'tt1', 672 | _t2 => 'tt2', 673 | _t3 => 456, 674 | _t4 => 5 675 | } 676 | ], 677 | status => 'ok', 678 | code => 0, 679 | sync => 5 680 | 681 | =over 4 682 | 683 | =item count 684 | 685 | Tuples count 686 | 687 | =item tuples 688 | 689 | Tuples themeselves 690 | 691 | =item status 692 | 693 | Status string ('ok') 694 | 695 | =item code 696 | 697 | Return code (0 if ok, else error code (L)) 698 | 699 | =item sync 700 | 701 | Request id 702 | 703 | =back 704 | 705 | =cut 706 | 707 | 708 | =head2 Error result 709 | 710 | [undef, $error_msg, { 711 | errstr => $error_msg, 712 | status => 'error', 713 | code => $error_code, 714 | sync => 3 715 | }] 716 | 717 | =over 4 718 | 719 | =item errstr 720 | 721 | Error string 722 | 723 | =item status 724 | 725 | Status string ('error') 726 | 727 | =item code 728 | 729 | Return code (0 if ok, else error code (L)) 730 | 731 | =item sync 732 | 733 | Request id 734 | 735 | =back 736 | 737 | =cut 738 | 739 | 740 | =head1 AUTHOR 741 | 742 | igorcoding, Eigorcoding@gmail.comE, 743 | Mons Anderson, Emons@cpan.orgE 744 | 745 | =head1 BUGS 746 | 747 | Please report any bugs or feature requests in L 748 | 749 | =head1 COPYRIGHT AND LICENSE 750 | 751 | Copyright (C) 2015 by igorcoding 752 | 753 | This program is released under the following license: GPL 754 | 755 | =cut 756 | 757 | 1; 758 | -------------------------------------------------------------------------------- /lib/EV/Tarantool16/Multi.pm: -------------------------------------------------------------------------------- 1 | package EV::Tarantool16::Multi; 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | no warnings 'uninitialized'; 7 | use Scalar::Util qw(weaken); 8 | use EV::Tarantool16; 9 | use Carp; 10 | sub U(@) { $_[0] } 11 | 12 | sub log_err {} 13 | sub log_warn { 14 | my $self = shift; 15 | warn "@_\n" if $self->{log_level} >= 2; 16 | } 17 | 18 | sub new { 19 | my $pkg = shift; 20 | my $self = bless { 21 | timeout => 1, 22 | reconnect => 1/3, 23 | cnntrace => 1, 24 | ares_reuse => 0, 25 | wbuf_limit => 16000, 26 | servers => [], 27 | log_level => 3, 28 | one_connected => undef, 29 | connected => undef, 30 | all_connected => undef, 31 | one_disconnected => undef, 32 | disconnected => undef, 33 | all_disconnected => undef, 34 | @_, 35 | stores => [], 36 | connected_mode => 'any', 37 | },$pkg; 38 | 39 | my $servers = delete $self->{servers}; 40 | $self->{servers} = []; 41 | 42 | my $i = 0; 43 | my $rws = 0; 44 | my $ros = 0; 45 | for (@$servers) { 46 | my $srv; 47 | my $id = $i++; 48 | if (ref) { 49 | $srv = { %$_, id => $id }; 50 | } else { 51 | m{^(?:([^:]*?):([^:]*?)@)?([^:]+)(?::(\d+))}; 52 | $srv = { 53 | rw => 1, 54 | username => $1, 55 | password => $2, 56 | host => $3, 57 | port => $4 // 3301, 58 | id => $id, 59 | gen => 1, 60 | }; 61 | } 62 | $srv->{node} = ($srv->{rw} ? 'rw' : 'ro' ) . ':' . $srv->{host} . ':' . $srv->{port}; 63 | if ($srv->{rw}) { $rws++ } else { $ros++; } 64 | push @{$self->{servers}}, $srv; 65 | my $warned; 66 | $srv->{c} = EV::Tarantool16->new({ 67 | username => $srv->{username}, 68 | password => $srv->{password}, 69 | host => $srv->{host}, 70 | port => $srv->{port}, 71 | timeout => $self->{timeout}, 72 | reconnect => $self->{reconnect}, 73 | read_buffer => 2*1024*1024, 74 | cnntrace => $self->{cnntrace}, 75 | ares_reuse => $self->{ares_reuse}, 76 | wbuf_limit => $self->{wbuf_limit}, 77 | log_level => $self->{log_level}, 78 | connected => sub { 79 | my $c = shift; 80 | @{ $srv->{peer} = {} }{qw(host port)} = @_; 81 | 82 | $self->_db_online( $srv ); 83 | }, 84 | connfail => sub { 85 | my ($c,$fail) = @_; 86 | $self->{connfail} ? $self->{connfail}( U($self,$c),$fail ) : 87 | !$warned++ && $self->log_warn("Connection to $srv->{node} failed: $fail"); 88 | }, 89 | disconnected => sub { 90 | my $c = shift; 91 | $srv->{gen}++; 92 | @_ and $srv->{peer} and $self->log_warn("Connection to $srv->{node}/$srv->{peer}{host}:$srv->{peer}{port} closed: @_"); 93 | $self->_db_offline( $srv, @_ ); 94 | 95 | }, 96 | }); 97 | } 98 | if (not $ros+$rws ) { 99 | die "Cluster could not ever be 'connected' since have no servers (@{$servers})\n"; 100 | } 101 | 102 | return $self; 103 | } 104 | 105 | sub connect : method { 106 | my $self = shift; 107 | for my $srv (@{ $self->{servers} }) { 108 | $srv->{c}->connect; 109 | } 110 | } 111 | 112 | sub disconnect : method { 113 | my $self = shift; 114 | for my $srv (@{ $self->{servers} }) { 115 | $srv->{c}->disconnect; 116 | } 117 | } 118 | 119 | sub ok { 120 | my $self = shift; 121 | if (@_ and $_[0] ne 'any') { 122 | return @{ $self->{$_[0].'stores'} } > 0 ? 1 : 0; 123 | } else { 124 | return @{ $self->{stores} } > 0 ? 1 : 0; 125 | } 126 | } 127 | 128 | sub _db_online { 129 | my $self = shift; 130 | my $srv = shift; 131 | 132 | my $first = ( 133 | ( @{ $self->{stores} } == 0 ) 134 | ) || 0; 135 | 136 | push @{ $self->{stores} }, $srv; 137 | 138 | my $event = "one_connected"; 139 | my @args = ( U($self,$srv->{c}), @{ $srv->{peer} }{qw(host port)} ); 140 | 141 | $self->{$event} && $self->{$event}( @args ); 142 | $first and $self->{connected} and $self->{connected}( @args ); 143 | 144 | if ( $self->{all_connected} and @{ $self->{servers} } == @{ $self->{stores} } ) { 145 | $self->{all_connected}( $self, $self->{stores} ); 146 | } 147 | } 148 | 149 | sub _db_offline { 150 | my $self = shift; 151 | my $srv = shift; 152 | my $c = $srv->{c}; 153 | 154 | $self->{stores} = [ grep $_ != $srv, @{ $self->{stores} } ]; 155 | 156 | my $last = ( 157 | ( @{ $self->{stores} } == 0 ) 158 | ) || 0; 159 | 160 | my $event = "one_disconnected"; 161 | 162 | my @args = ( U($self,$srv->{c}), @_ ); 163 | $self->{$event} && $self->{$event}( @args ); 164 | 165 | $last and $self->{disconnected} and $self->{disconnected}( @args ); 166 | 167 | if( $self->{all_disconnected} and @{ $self->{stores} } == 0 ) { 168 | $self->{all_disconnected}( $self ); 169 | } 170 | } 171 | 172 | =for rem 173 | RW - send request only to RW node 174 | RO - send request only to RO node 175 | ANY - send request to any node 176 | ARO - send request to any node, but prefer ro 177 | ARW - send request to any node, but prefer rw 178 | =cut 179 | 180 | sub _srv_by_mode { 181 | my $self = shift; 182 | 183 | my $mode = $self->{connected_mode}; 184 | my $srv; 185 | 186 | @{ $self->{stores} } or do { $_[-1]( undef, "Have no connected nodes for mode $mode" ), return }; 187 | $srv = $self->{stores}[ rand @{ $self->{stores} } ]; 188 | my $cb = pop; 189 | return $srv->{c}, $cb; 190 | } 191 | 192 | sub _srv_rw { 193 | my $self = shift; 194 | my $mode = $self->{connected_mode}; 195 | 196 | @{ $self->{stores} } or do { $_[-1]( undef, "Have no connected nodes for mode rw" ), return }; 197 | my $srv = $self->{stores}[ rand @{ $self->{stores} } ]; 198 | 199 | my $cb = pop; 200 | return $srv->{c}, $cb; 201 | } 202 | 203 | sub ping : method { 204 | my ($srv,$cb) = &_srv_by_mode or return; 205 | $srv->ping(@_,$cb); 206 | } 207 | 208 | sub eval : method { 209 | my ($srv,$cb) = &_srv_by_mode or return; 210 | $srv->eval(@_,$cb); 211 | } 212 | 213 | sub call : method { 214 | my ($srv,$cb) = &_srv_by_mode or return; 215 | $srv->call(@_,$cb); 216 | } 217 | 218 | sub lua : method { 219 | my ($srv,$cb) = &_srv_by_mode or return; 220 | $srv->lua(@_,$cb); 221 | } 222 | 223 | sub select : method { 224 | my ($srv,$cb) = &_srv_by_mode or return; 225 | $srv->select(@_,$cb); 226 | } 227 | 228 | sub insert : method { 229 | my ($srv,$cb) = &_srv_rw or return; 230 | $srv->insert(@_,$cb); 231 | } 232 | 233 | sub delete : method { 234 | my ($srv,$cb) = &_srv_rw or return; 235 | $srv->delete(@_,$cb); 236 | } 237 | 238 | sub update : method { 239 | my ($srv,$cb) = &_srv_rw or return; 240 | $srv->update(@_,$cb); 241 | } 242 | 243 | sub each : method { 244 | my $self = shift; 245 | my $cb = pop; 246 | my $flags = shift; 247 | for my $s (@{ $self->{stores} }) { 248 | $cb->($s); 249 | } 250 | } 251 | 252 | 253 | 254 | 1; 255 | -------------------------------------------------------------------------------- /libs/crypto/base64.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Redistribution and use in source and binary forms, with or 3 | * without modification, are permitted provided that the following 4 | * conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above 7 | * copyright notice, this list of conditions and the 8 | * following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following 12 | * disclaimer in the documentation and/or other materials 13 | * provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 17 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 19 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 20 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 23 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 26 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | * SUCH DAMAGE. 28 | */ 29 | #include "base64.h" 30 | /* 31 | * This is part of the libb64 project, and has been placed in the 32 | * public domain. For details, see 33 | * http://sourceforge.net/projects/libb64 34 | */ 35 | 36 | /* {{{ encode */ 37 | 38 | enum base64_encodestep { step_A, step_B, step_C }; 39 | 40 | struct base64_encodestate { 41 | enum base64_encodestep step; 42 | char result; 43 | int stepcount; 44 | }; 45 | 46 | static inline void 47 | base64_encodestate_init(struct base64_encodestate *state) 48 | { 49 | state->step = step_A; 50 | state->result = 0; 51 | state->stepcount = 0; 52 | } 53 | 54 | static inline char 55 | base64_encode_value(char value) 56 | { 57 | static const char encoding[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 58 | unsigned codepos = (unsigned) value; 59 | if (codepos > sizeof(encoding) - 1) 60 | return '='; 61 | return encoding[codepos]; 62 | } 63 | 64 | static int 65 | base64_encode_block(const char *in_bin, int in_len, 66 | char *out_base64, int out_len, 67 | struct base64_encodestate *state) 68 | { 69 | const char *const in_end = in_bin + in_len; 70 | const char *in_pos = in_bin; 71 | char *out_pos = out_base64; 72 | char *out_end = out_base64 + out_len; 73 | char result; 74 | char fragment; 75 | 76 | result = state->result; 77 | 78 | switch (state->step) 79 | { 80 | while (1) 81 | { 82 | case step_A: 83 | if (in_pos == in_end || out_pos >= out_end) { 84 | state->result = result; 85 | state->step = step_A; 86 | return out_pos - out_base64; 87 | } 88 | fragment = *in_pos++; 89 | result = (fragment & 0x0fc) >> 2; 90 | *out_pos++ = base64_encode_value(result); 91 | result = (fragment & 0x003) << 4; 92 | case step_B: 93 | if (in_pos == in_end || out_pos >= out_end) { 94 | state->result = result; 95 | state->step = step_B; 96 | return out_pos - out_base64; 97 | } 98 | fragment = *in_pos++; 99 | result |= (fragment & 0x0f0) >> 4; 100 | *out_pos++ = base64_encode_value(result); 101 | result = (fragment & 0x00f) << 2; 102 | case step_C: 103 | if (in_pos == in_end || out_pos + 2 >= out_end) { 104 | state->result = result; 105 | state->step = step_C; 106 | return out_pos - out_base64; 107 | } 108 | fragment = *in_pos++; 109 | result |= (fragment & 0x0c0) >> 6; 110 | *out_pos++ = base64_encode_value(result); 111 | result = (fragment & 0x03f) >> 0; 112 | *out_pos++ = base64_encode_value(result); 113 | 114 | /* 115 | * Each full step (A->B->C) yields 116 | * 4 characters. 117 | */ 118 | if (++state->stepcount * 4 == BASE64_CHARS_PER_LINE) { 119 | if (out_pos >= out_end) 120 | return out_pos - out_base64; 121 | *out_pos++ = '\n'; 122 | state->stepcount = 0; 123 | } 124 | } 125 | } 126 | /* control should not reach here */ 127 | return out_pos - out_base64; 128 | } 129 | 130 | static int 131 | base64_encode_blockend(char *out_base64, int out_len, 132 | struct base64_encodestate *state) 133 | { 134 | char *out_pos = out_base64; 135 | char *out_end = out_base64 + out_len; 136 | 137 | switch (state->step) { 138 | case step_B: 139 | if (out_pos + 2 >= out_end) 140 | return out_pos - out_base64; 141 | *out_pos++ = base64_encode_value(state->result); 142 | *out_pos++ = '='; 143 | *out_pos++ = '='; 144 | break; 145 | case step_C: 146 | if (out_pos + 1 >= out_end) 147 | return out_pos - out_base64; 148 | *out_pos++ = base64_encode_value(state->result); 149 | *out_pos++ = '='; 150 | break; 151 | case step_A: 152 | break; 153 | } 154 | if (out_pos >= out_end) 155 | return out_pos - out_base64; 156 | #if 0 157 | /* Sometimes the output is useful without a newline. */ 158 | *out_pos++ = '\n'; 159 | if (out_pos >= out_end) 160 | return out_pos - out_base64; 161 | #endif 162 | *out_pos = '\0'; 163 | return out_pos - out_base64; 164 | } 165 | 166 | int 167 | base64_encode(const char *in_bin, int in_len, 168 | char *out_base64, int out_len) 169 | { 170 | struct base64_encodestate state; 171 | base64_encodestate_init(&state); 172 | int res = base64_encode_block(in_bin, in_len, out_base64, 173 | out_len, &state); 174 | return res + base64_encode_blockend(out_base64 + res, out_len - res, 175 | &state); 176 | } 177 | 178 | /* }}} */ 179 | 180 | /* {{{ decode */ 181 | 182 | enum base64_decodestep { step_a, step_b, step_c, step_d }; 183 | 184 | struct base64_decodestate 185 | { 186 | enum base64_decodestep step; 187 | char result; 188 | }; 189 | 190 | static char 191 | base64_decode_value(char value) 192 | { 193 | static const char decoding[] = { 194 | 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 195 | 59, 60, 61, -1, -1, -1, -2, -1, -1, -1, 0, 1, 196 | 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 197 | 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 198 | -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 199 | 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 200 | 44, 45, 46, 47, 48, 49, 50, 51 201 | }; 202 | static const char decoding_size = sizeof(decoding); 203 | int codepos = (signed char) value; 204 | codepos -= 43; 205 | if (codepos < 0 || codepos > decoding_size) 206 | return -1; 207 | return decoding[codepos]; 208 | } 209 | 210 | static inline void 211 | base64_decodestate_init(struct base64_decodestate *state) 212 | { 213 | state->step = step_a; 214 | state->result = 0; 215 | } 216 | 217 | static int 218 | base64_decode_block(const char *in_base64, int in_len, 219 | char *out_bin, int out_len, 220 | struct base64_decodestate *state) 221 | { 222 | const char *in_pos = in_base64; 223 | const char *in_end = in_base64 + in_len; 224 | char *out_pos = out_bin; 225 | char *out_end = out_bin + out_len; 226 | char fragment; 227 | 228 | *out_pos = state->result; 229 | 230 | switch (state->step) 231 | { 232 | while (1) 233 | { 234 | case step_a: 235 | do { 236 | if (in_pos == in_end || out_pos >= out_end) 237 | { 238 | state->step = step_a; 239 | state->result = *out_pos; 240 | return out_pos - out_bin; 241 | } 242 | fragment = base64_decode_value(*in_pos++); 243 | } while (fragment < 0); 244 | *out_pos = (fragment & 0x03f) << 2; 245 | case step_b: 246 | do { 247 | if (in_pos == in_end || out_pos >= out_end) 248 | { 249 | state->step = step_b; 250 | state->result = *out_pos; 251 | return out_pos - out_bin; 252 | } 253 | fragment = base64_decode_value(*in_pos++); 254 | } while (fragment < 0); 255 | *out_pos++ |= (fragment & 0x030) >> 4; 256 | if (out_pos < out_end) 257 | *out_pos = (fragment & 0x00f) << 4; 258 | case step_c: 259 | do { 260 | if (in_pos == in_end || out_pos >= out_end) 261 | { 262 | state->step = step_c; 263 | state->result = *out_pos; 264 | return out_pos - out_bin; 265 | } 266 | fragment = base64_decode_value(*in_pos++); 267 | } while (fragment < 0); 268 | *out_pos++ |= (fragment & 0x03c) >> 2; 269 | if (out_pos < out_end) 270 | *out_pos = (fragment & 0x003) << 6; 271 | case step_d: 272 | do { 273 | if (in_pos == in_end || out_pos >= out_end) 274 | { 275 | state->step = step_d; 276 | state->result = *out_pos; 277 | return out_pos - out_bin; 278 | } 279 | fragment = base64_decode_value(*in_pos++); 280 | } while (fragment < 0); 281 | *out_pos++ |= (fragment & 0x03f); 282 | } 283 | } 284 | /* control should not reach here */ 285 | return out_pos - out_bin; 286 | } 287 | 288 | 289 | 290 | int 291 | base64_decode(const char *in_base64, int in_len, 292 | char *out_bin, int out_len) 293 | { 294 | struct base64_decodestate state; 295 | base64_decodestate_init(&state); 296 | return base64_decode_block(in_base64, in_len, 297 | out_bin, out_len, &state); 298 | } 299 | 300 | /* }}} */ 301 | -------------------------------------------------------------------------------- /libs/crypto/base64.h: -------------------------------------------------------------------------------- 1 | #ifndef BASE64_H 2 | #define BASE64_H 3 | /* 4 | * Redistribution and use in source and binary forms, with or 5 | * without modification, are permitted provided that the following 6 | * conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above 9 | * copyright notice, this list of conditions and the 10 | * following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials 15 | * provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | /* 32 | * This is part of the libb64 project, and has been placed in the 33 | * public domain. For details, see 34 | * http://sourceforge.net/projects/libb64 35 | */ 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif 39 | 40 | #define BASE64_CHARS_PER_LINE 72 41 | 42 | static inline int 43 | base64_bufsize(int binsize) 44 | { 45 | int datasize = binsize * 4/3 + 4; 46 | int newlines = ((datasize + BASE64_CHARS_PER_LINE - 1)/ 47 | BASE64_CHARS_PER_LINE); 48 | return datasize + newlines; 49 | } 50 | 51 | /** 52 | * Encode a binary stream into BASE64 text. 53 | * 54 | * @pre the buffer size is at least 4/3 of the stream 55 | * size + stream_size/72 (newlines) + 4 56 | * 57 | * @param[in] in_bin the binary input stream to decode 58 | * @param[in] in_len size of the input 59 | * @param[out] out_base64 output buffer for the encoded data 60 | * @param[in] out_len buffer size, must be at least 61 | * 4/3 of the input size 62 | * 63 | * @return the size of encoded output 64 | */ 65 | 66 | int 67 | base64_encode(const char *in_bin, int in_len, 68 | char *out_base64, int out_len); 69 | 70 | /** 71 | * Decode a BASE64 text into a binary 72 | * 73 | * @param[in] in_base64 the BASE64 stream to decode 74 | * @param[in] in_len size of the input 75 | * @param[out] out_bin output buffer size 76 | * @param[in] out_len buffer size 77 | * 78 | * @pre the output buffer size must be at least 79 | * 3/4 + 1 of the size of the input 80 | * 81 | * @return the size of decoded output 82 | */ 83 | 84 | int base64_decode(const char *in_base64, int in_len, 85 | char *out_bin, int out_len); 86 | 87 | #ifdef __cplusplus 88 | } /* extern "C" */ 89 | #endif 90 | 91 | #include "base64.c" 92 | #endif /* BASE64_H */ 93 | 94 | -------------------------------------------------------------------------------- /libs/crypto/sha1.c: -------------------------------------------------------------------------------- 1 | 2 | /* from valgrind tests */ 3 | 4 | /* ================ sha1.c ================ */ 5 | /* 6 | SHA-1 in C 7 | By Steve Reid 8 | 100% Public Domain 9 | 10 | Test Vectors (from FIPS PUB 180-1) 11 | "abc" 12 | A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D 13 | "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 14 | 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 15 | A million repetitions of "a" 16 | 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F 17 | */ 18 | 19 | /* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ 20 | /* #define SHA1HANDSOFF * Copies data before messing with it. */ 21 | 22 | #define SHA1HANDSOFF 23 | 24 | #include 25 | #include 26 | #include /* for u_int*_t */ 27 | #include "sha1.h" 28 | 29 | #define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) 30 | 31 | /* blk0() and blk() perform the initial expand. */ 32 | /* I got the idea of expanding during the round function from SSLeay */ 33 | #define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ 34 | |(rol(block->l[i],8)&0x00FF00FF)) 35 | 36 | #define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ 37 | ^block->l[(i+2)&15]^block->l[i&15],1)) 38 | 39 | /* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ 40 | #define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); 41 | #define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); 42 | #define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); 43 | #define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); 44 | #define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); 45 | 46 | 47 | /* Hash a single 512-bit block. This is the core of the algorithm. */ 48 | 49 | void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) 50 | { 51 | uint32_t a, b, c, d, e; 52 | typedef union { 53 | unsigned char c[64]; 54 | uint32_t l[16]; 55 | } CHAR64LONG16; 56 | #ifdef SHA1HANDSOFF 57 | CHAR64LONG16 block[1]; /* use array to appear as a pointer */ 58 | memcpy(block, buffer, 64); 59 | #else 60 | /* The following had better never be used because it causes the 61 | * pointer-to-const buffer to be cast into a pointer to non-const. 62 | * And the result is written through. I threw a "const" in, hoping 63 | * this will cause a diagnostic. 64 | */ 65 | CHAR64LONG16* block = (const CHAR64LONG16*)buffer; 66 | #endif 67 | /* Copy context->state[] to working vars */ 68 | a = state[0]; 69 | b = state[1]; 70 | c = state[2]; 71 | d = state[3]; 72 | e = state[4]; 73 | /* 4 rounds of 20 operations each. Loop unrolled. */ 74 | R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); 75 | R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); 76 | R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); 77 | R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); 78 | R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); 79 | R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); 80 | R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); 81 | R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); 82 | R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); 83 | R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); 84 | R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); 85 | R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); 86 | R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); 87 | R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); 88 | R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); 89 | R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); 90 | R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); 91 | R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); 92 | R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); 93 | R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); 94 | /* Add the working vars back into context.state[] */ 95 | state[0] += a; 96 | state[1] += b; 97 | state[2] += c; 98 | state[3] += d; 99 | state[4] += e; 100 | /* Wipe variables */ 101 | a = b = c = d = e = 0; 102 | #ifdef SHA1HANDSOFF 103 | memset(block, '\0', sizeof(block)); 104 | #endif 105 | } 106 | 107 | 108 | /* SHA1Init - Initialize new context */ 109 | 110 | void SHA1Init(SHA1_CTX* context) 111 | { 112 | /* SHA1 initialization constants */ 113 | context->state[0] = 0x67452301; 114 | context->state[1] = 0xEFCDAB89; 115 | context->state[2] = 0x98BADCFE; 116 | context->state[3] = 0x10325476; 117 | context->state[4] = 0xC3D2E1F0; 118 | context->count[0] = context->count[1] = 0; 119 | } 120 | 121 | 122 | /* Run your data through this. */ 123 | 124 | void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len) 125 | { 126 | uint32_t i, j; 127 | 128 | j = context->count[0]; 129 | if ((context->count[0] += len << 3) < j) 130 | context->count[1]++; 131 | context->count[1] += (len>>29); 132 | j = (j >> 3) & 63; 133 | if ((j + len) > 63) { 134 | memcpy(&context->buffer[j], data, (i = 64-j)); 135 | SHA1Transform(context->state, context->buffer); 136 | for ( ; i + 63 < len; i += 64) { 137 | SHA1Transform(context->state, &data[i]); 138 | } 139 | j = 0; 140 | } 141 | else i = 0; 142 | memcpy(&context->buffer[j], &data[i], len - i); 143 | } 144 | 145 | 146 | /* Add padding and return the message digest. */ 147 | 148 | void SHA1Final(unsigned char digest[20], SHA1_CTX* context) 149 | { 150 | unsigned i; 151 | unsigned char finalcount[8]; 152 | unsigned char c; 153 | 154 | #if 0 /* untested "improvement" by DHR */ 155 | /* Convert context->count to a sequence of bytes 156 | * in finalcount. Second element first, but 157 | * big-endian order within element. 158 | * But we do it all backwards. 159 | */ 160 | unsigned char *fcp = &finalcount[8]; 161 | 162 | for (i = 0; i < 2; i++) 163 | { 164 | uint32_t t = context->count[i]; 165 | int j; 166 | 167 | for (j = 0; j < 4; t >>= 8, j++) 168 | *--fcp = (unsigned char) t; 169 | } 170 | #else 171 | for (i = 0; i < 8; i++) { 172 | finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] 173 | >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ 174 | } 175 | #endif 176 | c = 0200; 177 | SHA1Update(context, &c, 1); 178 | while ((context->count[0] & 504) != 448) { 179 | c = 0000; 180 | SHA1Update(context, &c, 1); 181 | } 182 | SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ 183 | for (i = 0; i < 20; i++) { 184 | digest[i] = (unsigned char) 185 | ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); 186 | } 187 | /* Wipe variables */ 188 | memset(context, '\0', sizeof(*context)); 189 | memset(&finalcount, '\0', sizeof(finalcount)); 190 | } 191 | /* ================ end of sha1.c ================ */ 192 | 193 | #if 0 194 | #define BUFSIZE 4096 195 | 196 | int 197 | main(int argc, char **argv) 198 | { 199 | SHA1_CTX ctx; 200 | unsigned char hash[20], buf[BUFSIZE]; 201 | int i; 202 | 203 | for(i=0;i 5 | 6 | /* ================ sha1.h ================ */ 7 | /* 8 | SHA-1 in C 9 | By Steve Reid 10 | 100% Public Domain 11 | */ 12 | 13 | typedef struct { 14 | uint32_t state[5]; 15 | uint32_t count[2]; 16 | unsigned char buffer[64]; 17 | } SHA1_CTX; 18 | 19 | void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]); 20 | void SHA1Init(SHA1_CTX* context); 21 | void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len); 22 | void SHA1Final(unsigned char digest[20], SHA1_CTX* context); 23 | 24 | #include "sha1.c" 25 | #endif 26 | -------------------------------------------------------------------------------- /makeall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | MODULE=`perl -ne 'print($1),exit if m{version_from.+?([\w/.]+)}i' Makefile.PL`; 4 | perl=perl 5 | $perl -v 6 | 7 | rm -rf dist && mkdir dist 8 | rm -rf MANIFEST.bak Makefile.old MYMETA.* META.* && \ 9 | AUTHOR=1 $perl Makefile.PL && \ 10 | make manifest && \ 11 | cp MYMETA.yml META.yml && \ 12 | cp MYMETA.json META.json && \ 13 | make && \ 14 | #TEST_AUTHOR=1 make test && \ 15 | #TEST_AUTHOR=1 runprove 'xt/*.t' && \ 16 | make disttest && \ 17 | make dist && \ 18 | mv -f *.tar.gz dist/ && \ 19 | make clean && \ 20 | cp META.yml MYMETA.yml && \ 21 | cp META.json MYMETA.json && \ 22 | rm -rf MANIFEST.bak Makefile.old && \ 23 | echo "All is OK" 24 | -------------------------------------------------------------------------------- /provision/travis.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 4 | 5 | echo "Travis OS: ${TRAVIS_OS_NAME}" 6 | 7 | TestTarantool_VER=0.033 8 | TestTarantool_URL=https://github.com/igorcoding/Test-Tarantool16/releases/download/v${TestTarantool_VER}/Test-Tarantool16-${TestTarantool_VER}.tar.gz 9 | TestTarantool_LOCATION=/tmp/test-tarantool16.tar.gz 10 | wget ${TestTarantool_URL} -O ${TestTarantool_LOCATION} 11 | 12 | if [ -z "$TRAVIS_OS_NAME" ] || [ ${TRAVIS_OS_NAME} == 'linux' ]; then 13 | sudo apt-get install -y curl 14 | curl https://packagecloud.io/tarantool/1_7/gpgkey | sudo apt-key add - 15 | 16 | sudo apt-get -y install apt-transport-https 17 | release=`lsb_release -c -s` 18 | 19 | sudo rm -f /etc/apt/sources.list.d/*tarantool*.list 20 | sudo bash -c 'cat > /etc/apt/sources.list.d/tarantool.list <<- EOF 21 | deb https://packagecloud.io/tarantool/1_7/ubuntu/ `lsb_release -c -s` main 22 | deb-src https://packagecloud.io/tarantool/1_7/ubuntu/ `lsb_release -c -s` main 23 | EOF' 24 | 25 | sudo apt-get update 26 | sudo apt-get install -y tarantool 27 | 28 | USR_SRC=/usr/local/src 29 | wget http://c-ares.haxx.se/download/c-ares-1.10.0.tar.gz -O - | sudo tar -C ${USR_SRC} -xzvf - 30 | cd ${USR_SRC}/c-ares-1.10.0 31 | sudo ./configure 32 | sudo make 33 | sudo make install 34 | 35 | elif [ ${TRAVIS_OS_NAME} == 'osx' ]; then 36 | echo "Mac OS X build is not supported" 37 | # sudo sh -c 'echo "127.0.0.1 localhost" >> /etc/hosts' 38 | # sudo ifconfig lo0 alias 127.0.0.2 up 39 | # brew update 40 | # brew install curl 41 | # brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/master/Formula/cpanminus.rb 42 | # brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/master/Formula/c-ares.rb 43 | # cpanm --version 44 | # cpanm --local-lib=~/perl5 local::lib && eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) 45 | 46 | # brew install tarantool 47 | # tarantool -V 48 | 49 | # # cat ~/.cpanm/work/**/*.log 50 | fi 51 | 52 | cpanm Types::Serialiser 53 | cpanm EV 54 | 55 | cpanm Test::More 56 | cpanm Test::Deep 57 | cpanm AnyEvent 58 | cpanm Proc::ProcessTable 59 | cpanm Time::HiRes 60 | cpanm Scalar::Util 61 | cpanm Data::Dumper 62 | cpanm Carp 63 | cpanm ${TestTarantool_LOCATION} 64 | -------------------------------------------------------------------------------- /provision/vagrant.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 3 | 4 | export LANG=en_US.UTF-8 5 | export LANGUAGE=en_US:en 6 | export LC_ALL=en_US.UTF-8 7 | sudo locale-gen en_US.UTF-8 8 | 9 | sudo bash -c 'cat >> ~/.bashrc <<- EOF 10 | export LANG=en_US.UTF-8 11 | export LANGUAGE=en_US:en 12 | export LC_ALL=en_US.UTF-8 13 | EOF' 14 | 15 | function install_tarantool { 16 | curl http://download.tarantool.org/tarantool/1.7/gpgkey | sudo apt-key add - 17 | release=`lsb_release -c -s` 18 | # install https download transport for APT 19 | sudo apt-get -y install apt-transport-https 20 | 21 | # append two lines to a list of source repositories 22 | sudo rm -f /etc/apt/sources.list.d/*tarantool*.list 23 | sudo tee /etc/apt/sources.list.d/tarantool_1_7.list <<- EOF 24 | deb http://download.tarantool.org/tarantool/1.7/ubuntu/ $release main 25 | deb-src http://download.tarantool.org/tarantool/1.7/ubuntu/ $release main 26 | EOF 27 | 28 | # install 29 | sudo apt-get update 30 | sudo apt-get -y install tarantool 31 | tarantool --version 32 | 33 | mkdir -p ${HOME}/tnt 34 | } 35 | 36 | function install_cares { 37 | USR_SRC=/usr/local/src 38 | wget http://c-ares.haxx.se/download/c-ares-1.10.0.tar.gz -O - | sudo tar -C ${USR_SRC} -xzvf - 39 | cd ${USR_SRC}/c-ares-1.10.0 40 | sudo ./configure 41 | sudo make 42 | sudo make install 43 | } 44 | 45 | install_tarantool 46 | install_cares 47 | 48 | TestTarantool_VER=0.033 49 | TestTarantool_URL=https://github.com/igorcoding/Test-Tarantool16/releases/download/v${TestTarantool_VER}/Test-Tarantool16-${TestTarantool_VER}.tar.gz 50 | TestTarantool_LOCATION=/tmp/test-tarantool16.tar.gz 51 | wget ${TestTarantool_URL} -O ${TestTarantool_LOCATION} 52 | 53 | 54 | sudo apt-get install -y valgrind perl-doc libexpat1-dev 55 | curl -L https://cpanmin.us | sudo perl - App::cpanminus 56 | sudo chown -R vagrant:vagrant ~/.cpanm 57 | cpanm --local-lib=~/perl5 local::lib && eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) 58 | echo 'eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)' >> $HOME/.bashrc 59 | 60 | cpanm Types::Serialiser 61 | cpanm EV 62 | cpanm Test::More 63 | cpanm Test::Deep 64 | cpanm AnyEvent 65 | cpanm Proc::ProcessTable 66 | cpanm Time::HiRes 67 | cpanm Scalar::Util 68 | cpanm Data::Dumper 69 | cpanm Carp 70 | cpanm ${TestTarantool_LOCATION} 71 | 72 | cpanm Test::Valgrind 73 | cpanm List::BinarySearch 74 | 75 | 76 | # echo 'Building Perl 5.16.3...' 77 | # mkdir -p ${HOME}/perl 78 | # mkdir -p ${HOME}/perl-src 79 | # cd ${HOME}/perl-src 80 | # wget http://www.cpan.org/src/5.0/perl-5.16.3.tar.gz -O - | tar -xzvf - 81 | # cd perl-5.16.3/ 82 | # ./Configure -des -Dprefix=${HOME}/perl -Duselargefiles -Duse64bitint -DUSEMYMALLOC -DDEBUGGING -DDEBUG_LEAKING_SCALARS -DPERL_MEM_LOG -Dinc_version_list=none -Doptimize="-march=athlon64 -fomit-frame-pointer -pipe -ggdb -g3 -O2" -Dccflags="-DPIC -fPIC -O2 -march=athlon64 -fomit-frame-pointer -pipe -ggdb -g3" -D locincpth="${HOME}/perl/include /usr/local/include" -D loclibpth="${HOME}/perl/lib /usr/local/lib" -D privlib=${HOME}/perl/lib/perl5/5.16.3 -D archlib=${HOME}/perl/lib/perl5/5.16.3 -D sitelib=${HOME}/perl/lib/perl5/5.16.3 -D sitearch=${HOME}/perl/lib/perl5/5.16.3 -Uinstallhtml1dir= -Uinstallhtml3dir= -Uinstallman1dir= -Uinstallman3dir= -Uinstallsitehtml1dir= -Uinstallsitehtml3dir= -Uinstallsiteman1dir= -Uinstallsiteman3dir= 83 | # # CLANG: # ./Configure -des -Dprefix=${HOME}/perl-llvm -Dcc=clang -Duselargefiles -Duse64bitint -DUSEMYMALLOC -DDEBUGGING -DDEBUG_LEAKING_SCALARS -DPERL_MEM_LOG -Dinc_version_list=none -Doptimize="-march=athlon64 -fomit-frame-pointer -pipe -ggdb -g3 -O2" -Dccflags="-DPIC -fPIC -O2 -march=athlon64 -fomit-frame-pointer -pipe -ggdb -g3" -D locincpth="${HOME}/perl-llvm/include /usr/local/include" -D loclibpth="${HOME}/perl-llvm/lib /usr/local/lib" -D privlib=${HOME}/perl-llvm/lib/perl5/5.16.3 -D archlib=${HOME}/perl-llvm/lib/perl5/5.16.3 -D sitelib=${HOME}/perl-llvm/lib/perl5/5.16.3 -D sitearch=${HOME}/perl-llvm/lib/perl5/5.16.3 -Uinstallhtml1dir= -Uinstallhtml3dir= -Uinstallman1dir= -Uinstallman3dir= -Uinstallsitehtml1dir= -Uinstallsitehtml3dir= -Uinstallsiteman1dir= -Uinstallsiteman3dir= 84 | # make 85 | # make install 86 | 87 | # ${HOME}/perl/bin/perl `which cpanm` Types::Serialiser 88 | # ${HOME}/perl/bin/perl `which cpanm` EV 89 | # ${HOME}/perl/bin/perl `which cpanm` EV::MakeMaker 90 | # ${HOME}/perl/bin/perl `which cpanm` AnyEvent 91 | # ${HOME}/perl/bin/perl `which cpanm` Test::Deep 92 | # ${HOME}/perl/bin/perl `which cpanm` Test::Valgrind 93 | # ${HOME}/perl/bin/perl `which cpanm` Devel::Leak 94 | -------------------------------------------------------------------------------- /rpm/perl-EV-Tarantool16.spec: -------------------------------------------------------------------------------- 1 | Name: perl-EV-Tarantool16 2 | Version: 0.1 3 | Release: 1%{?dist} 4 | Summary: EV::Tarantool16 5 | License: GPL+ 6 | Group: Development/Libraries 7 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 8 | BuildArch: x86_64 9 | BuildRequires: perl >= 0:5.006 10 | BuildRequires: tarantool >= 1.6.8.0 11 | BuildRequires: tarantool-devel >= 1.6.8.0 12 | BuildRequires: c-ares >= 1.10 13 | BuildRequires: c-ares-devel >= 1.10 14 | BuildRequires: libev-devel 15 | BuildRequires: perl(ExtUtils::MakeMaker) 16 | BuildRequires: perl(EV) 17 | BuildRequires: perl(Types::Serialiser) 18 | BuildRequires: perl(Test::More) 19 | BuildRequires: perl(Test::Deep) 20 | BuildRequires: perl(AnyEvent) 21 | BuildRequires: perl(Proc::ProcessTable) 22 | BuildRequires: perl(Time::HiRes) 23 | BuildRequires: perl(Scalar::Util) 24 | BuildRequires: perl(Data::Dumper) 25 | BuildRequires: perl(Carp) 26 | BuildRequires: perl(constant) 27 | Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) 28 | Requires: tarantool >= 1.6.8.0 29 | Requires: c-ares >= 1.10 30 | Requires: perl(EV) 31 | Requires: perl(Types::Serialiser) 32 | 33 | URL: https://github.com/igorcoding/EV-Tarantool16 34 | Source0: https://github.com/igorcoding/EV-Tarantool16/archive/%{version}/EV-Tarantool16-%{version}.tar.gz 35 | 36 | %description 37 | EV::Tarantool16 - connector for Tarantool 1.6+ 38 | 39 | %prep 40 | %setup -q -n EV-Tarantool16-%{version} 41 | 42 | %build 43 | %{__perl} Makefile.PL INSTALLDIRS=vendor 44 | make %{?_smp_mflags} 45 | 46 | %install 47 | rm -rf $RPM_BUILD_ROOT 48 | 49 | make pure_install PERL_INSTALL_ROOT=$RPM_BUILD_ROOT 50 | 51 | find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} \; 52 | find $RPM_BUILD_ROOT -depth -type d -exec rmdir {} 2>/dev/null \; 53 | 54 | %{_fixperms} $RPM_BUILD_ROOT/* 55 | 56 | %check 57 | TEST_FAST_MEM=1 make test 58 | 59 | %clean 60 | rm -rf $RPM_BUILD_ROOT 61 | 62 | %files 63 | %defattr(-,root,root,-) 64 | %doc META.json 65 | %{perl_vendorarch}/auto/* 66 | %{perl_vendorarch}/EV* 67 | %{_mandir}/man3/* 68 | 69 | %changelog 70 | * Sun Mar 25 2018 igorcoding 1.39-1 71 | - Create spec 72 | -------------------------------------------------------------------------------- /rpm/prebuild-el-7.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | echo "Installing Tarantool version=${VAR_TARANTOOL}" 4 | 5 | if [ -z "${VAR_TARANTOOL}" ]; then 6 | VAR_TARANTOOL="1.9" 7 | fi 8 | 9 | # Clean up yum cache 10 | sudo yum clean all 11 | 12 | # Enable EPEL repository 13 | sudo yum -y install http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm 14 | sudo sed 's/enabled=.*/enabled=1/g' -i /etc/yum.repos.d/epel.repo 15 | 16 | # Add Tarantool repository 17 | sudo rm -f /etc/yum.repos.d/*tarantool*.repo 18 | sudo tee /etc/yum.repos.d/tarantool_${VAR_TARANTOOL}.repo <<- EOF 19 | [tarantool] 20 | name=EnterpriseLinux-7 - Tarantool 21 | baseurl=http://download.tarantool.org/tarantool/${VAR_TARANTOOL}/el/7/x86_64/ 22 | gpgkey=http://download.tarantool.org/tarantool/${VAR_TARANTOOL}/gpgkey 23 | repo_gpgcheck=1 24 | gpgcheck=0 25 | enabled=1 26 | 27 | [tarantool-source] 28 | name=EnterpriseLinux-7 - Tarantool Sources 29 | baseurl=http://download.tarantool.org/tarantool/${VAR_TARANTOOL}/el/7/SRPMS 30 | gpgkey=http://download.tarantool.org/tarantool/${VAR_TARANTOOL}/gpgkey 31 | repo_gpgcheck=1 32 | gpgcheck=0 33 | EOF 34 | 35 | # Update metadata 36 | sudo yum makecache -y --disablerepo='*' --enablerepo='tarantool' --enablerepo='epel' 37 | 38 | # Install Tarantool 39 | sudo yum -y install tarantool 40 | -------------------------------------------------------------------------------- /rpm/prebuild-el.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | echo "Installing cpanm" 4 | sudo yum install -y perl-CPAN 5 | curl -L http://cpanmin.us | perl - --sudo App::cpanminus 6 | 7 | echo "Installing Test::Tarantool" 8 | TestTarantool_VER=0.033 9 | TestTarantool_URL=https://github.com/igorcoding/Test-Tarantool16/releases/download/v${TestTarantool_VER}/Test-Tarantool16-${TestTarantool_VER}.tar.gz 10 | TestTarantool_LOCATION=/tmp/test-tarantool16.tar.gz 11 | wget ${TestTarantool_URL} -O ${TestTarantool_LOCATION} 12 | cpanm --sudo ${TestTarantool_LOCATION} 13 | 14 | echo "Prebuild finished" 15 | -------------------------------------------------------------------------------- /t/00-use.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use FindBin; 5 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 6 | 7 | use Test::More tests => 2; 8 | BEGIN { 9 | use_ok('EV::Tarantool16'); 10 | use_ok('EV::Tarantool16::Multi'); 11 | }; 12 | -------------------------------------------------------------------------------- /t/01-basic.t: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | use 5.010; 4 | use strict; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use EV; 8 | use Time::HiRes 'sleep','time'; 9 | use Scalar::Util 'weaken'; 10 | use Errno; 11 | use EV::Tarantool16; 12 | use Test::More; 13 | BEGIN{ $ENV{TEST_FAST} and plan 'skip_all'; } 14 | use Test::Deep; 15 | use Data::Dumper; 16 | use Renewer; 17 | use Carp; 18 | use Test::Tarantool16; 19 | 20 | $EV::DIED = sub { 21 | warn "@_"; 22 | EV::unloop; 23 | exit; 24 | }; 25 | 26 | my %test_exec = ( 27 | ping => 1, 28 | eval => 1, 29 | call => 1, 30 | lua => 1, 31 | select => 1, 32 | insert => 1, 33 | replace => 1, 34 | delete => 1, 35 | update => 1, 36 | upsert => 1, 37 | RTREE => 1, 38 | ); 39 | 40 | my $cfs = 0; 41 | my $connected; 42 | my $disconnected; 43 | 44 | my $w = AnyEvent->signal (signal => "INT", cb => sub { exit 0 }); 45 | 46 | my $tnt = { 47 | name => 'tarantool_tester', 48 | port => 11723, 49 | host => '127.0.0.1', 50 | username => 'test_user', 51 | password => 'test_pass', 52 | initlua => do { 53 | my $file = 't/tnt/app.lua'; 54 | local $/ = undef; 55 | open my $f, "<", $file 56 | or die "could not open $file: $!"; 57 | my $d = <$f>; 58 | close $f; 59 | $d; 60 | } 61 | }; 62 | 63 | $tnt = Test::Tarantool16->new( 64 | title => $tnt->{name}, 65 | host => $tnt->{host}, 66 | port => $tnt->{port}, 67 | logger => sub { diag ( $tnt->{title},' ', @_ ) if $ENV{TEST_VERBOSE}; }, 68 | initlua => $tnt->{initlua}, 69 | wal_mode => 'write', 70 | on_die => sub { my $self = shift; fail "tarantool $self->{title} is dead!: $!"; exit 1; }, 71 | ); 72 | 73 | $tnt->start(timeout => 10, sub { 74 | my ($status, $desc) = @_; 75 | if ($status == 1) { 76 | EV::unloop; 77 | } else { 78 | diag Dumper \@_; 79 | } 80 | }); 81 | EV::loop; 82 | 83 | my $w; $w = EV::timer 1, 0, sub { 84 | undef $w; 85 | EV::unloop; 86 | }; 87 | EV::loop; 88 | 89 | $tnt->{cnntrace} = 0; 90 | my $SPACE_NAME = 'tester'; 91 | 92 | 93 | my $c; $c = EV::Tarantool16->new({ 94 | host => $tnt->{host}, 95 | port => $tnt->{port}, 96 | username => $tnt->{username}, 97 | password => $tnt->{password}, 98 | cnntrace => $tnt->{cnntrace}, 99 | reconnect => 0.2, 100 | log_level => $ENV{TEST_VERBOSE} ? 4 : 0, 101 | connected => sub { 102 | diag Dumper \@_ unless $_[0]; 103 | diag "connected: @_" if $ENV{TEST_VERBOSE}; 104 | $connected++ if defined $_[0]; 105 | EV::unloop; 106 | }, 107 | connfail => sub { 108 | my $err = 0+$!; 109 | is $err, Errno::ECONNREFUSED, 'connfail - refused' or diag "$!, $_[1]"; 110 | # $nc->(@_) if $cfs == 0; 111 | $cfs++; 112 | # and 113 | EV::unloop; 114 | }, 115 | disconnected => sub { 116 | diag "discon: @_ / $!" if $ENV{TEST_VERBOSE}; 117 | $disconnected++; 118 | EV::unloop; 119 | }, 120 | }); 121 | 122 | $c->connect; 123 | EV::loop; 124 | 125 | # my $t; $t = EV::timer 1.0, 0, sub { 126 | # # diag Dumper $c->spaces; 127 | # EV::unloop; 128 | # undef $t; 129 | # }; 130 | # EV::loop; 131 | 132 | Renewer::renew_tnt($c, $SPACE_NAME, sub { 133 | EV::unloop; 134 | }); 135 | EV::loop; 136 | 137 | ok $connected > 0, "Connection ok"; 138 | croak "Not connected normally" unless $connected > 0; 139 | 140 | 141 | 142 | subtest 'Ping tests', sub { 143 | plan( skip_all => 'skip') if !$test_exec{ping}; 144 | diag '==== Ping tests ===' if $ENV{TEST_VERBOSE}; 145 | 146 | my $_plan = [ 147 | [{}, [ 148 | { 149 | schema_id => ignore(), 150 | sync => ignore(), 151 | code => 0 152 | } 153 | ]], 154 | [2, [ 155 | undef, 156 | "Opts must be a HASHREF" 157 | ]], 158 | [[], [ 159 | undef, 160 | "Opts must be a HASHREF" 161 | ]], 162 | ["", [ 163 | undef, 164 | "Opts must be a HASHREF" 165 | ]], 166 | ]; 167 | 168 | for my $p (@$_plan) { 169 | my $finished = 0; 170 | $c->ping($p->[0], sub { 171 | my $a = $_[0]; 172 | cmp_deeply \@_, $p->[1]; 173 | EV::unloop; 174 | $finished = 1; 175 | }); 176 | if (!$finished) { 177 | EV::loop; 178 | } 179 | } 180 | 181 | 182 | }; 183 | 184 | subtest 'Eval tests', sub { 185 | plan( skip_all => 'skip') if !$test_exec{eval}; 186 | diag '==== Eval tests ====' if $ENV{TEST_VERBOSE}; 187 | $c->eval("return {'hey'}", [], sub { 188 | my $a = $_[0]; 189 | diag Dumper \@_ if !$a; 190 | cmp_deeply $a, { 191 | count => 1, 192 | tuples => [ ['hey'] ], 193 | status => 'ok', 194 | code => 0, 195 | sync => ignore(), 196 | schema_id => ignore(), 197 | }; 198 | EV::unloop; 199 | }); 200 | EV::loop; 201 | }; 202 | 203 | subtest 'Call tests', sub { 204 | plan( skip_all => 'skip') if !$test_exec{call}; 205 | diag '==== Call tests ====' if $ENV{TEST_VERBOSE}; 206 | $c->call('string_function', [], sub { 207 | my $a = $_[0]; 208 | diag Dumper \@_ if !$a; 209 | cmp_deeply $a, { 210 | count => 1, 211 | tuples => [ ['hello world'] ], 212 | status => 'ok', 213 | code => 0, 214 | sync => ignore(), 215 | schema_id => ignore(), 216 | }; 217 | EV::unloop; 218 | }); 219 | EV::loop; 220 | }; 221 | 222 | subtest 'Lua tests', sub { 223 | plan( skip_all => 'skip') if !$test_exec{lua}; 224 | diag '==== Lua tests ====' if $ENV{TEST_VERBOSE}; 225 | $c->lua('string_function', [], sub { 226 | my $a = $_[0]; 227 | diag Dumper \@_ if !$a; 228 | cmp_deeply $a, { 229 | count => 1, 230 | tuples => [ ['hello world'] ], 231 | status => 'ok', 232 | code => 0, 233 | sync => ignore(), 234 | schema_id => ignore(), 235 | }; 236 | EV::unloop; 237 | }); 238 | EV::loop; 239 | }; 240 | 241 | subtest 'Select tests', sub { 242 | plan( skip_all => 'skip') if !$test_exec{select}; 243 | diag '==== Select tests ====' if $ENV{TEST_VERBOSE}; 244 | 245 | 246 | my $_plan = [ 247 | [[], {hash => 0}, { 248 | count => 4, 249 | tuples => [ 250 | ['t1','t2',2,200,[ 1, 2, 3, 'str1', 4 ]], 251 | ['t1','t2',3,0,{35 => Types::Serialiser::false,33 => Types::Serialiser::true,key2 => 42,key1 => 'value1'}], 252 | ['t1','t2',17,-745,'heyo'], 253 | ['tt1','tt2',456,5,'s'] 254 | ], 255 | status => 'ok', 256 | code => 0, 257 | sync => ignore(), 258 | schema_id => ignore(), 259 | }], 260 | 261 | [{_t1=>'t1', _t2=>'t2'}, {hash => 1, iterator => EV::Tarantool16::INDEX_LE}, { 262 | count => 3, 263 | tuples => [ 264 | { 265 | _t1 => 't1', 266 | _t2 => 't2', 267 | _t3 => 17, 268 | _t4 => -745, 269 | _t5 => 'heyo', 270 | }, 271 | { 272 | _t1 => 't1', 273 | _t2 => 't2', 274 | _t3 => 3, 275 | _t4 => 0, 276 | _t5 => {35 => Types::Serialiser::false,33 => Types::Serialiser::true,key2 => 42,key1 => 'value1'}, 277 | }, 278 | { 279 | _t1 => 't1', 280 | _t2 => 't2', 281 | _t3 => 2, 282 | _t4 => 200, 283 | _t5 => [1,2,3,'str1',4], 284 | }, 285 | 286 | ], 287 | status => 'ok', 288 | code => 0, 289 | sync => ignore(), 290 | schema_id => ignore(), 291 | }] 292 | ]; 293 | 294 | for my $p (@$_plan) { 295 | $c->select($SPACE_NAME, $p->[0], $p->[1], sub { 296 | my $a = $_[0]; 297 | diag Dumper \@_ if !$a; 298 | cmp_deeply $a, $p->[2]; 299 | EV::unloop; 300 | }); 301 | EV::loop; 302 | } 303 | }; 304 | 305 | 306 | subtest 'Insert tests', sub { 307 | plan( skip_all => 'skip') if !$test_exec{insert}; 308 | diag '==== Insert tests ====' if $ENV{TEST_VERBOSE}; 309 | 310 | my $_plan = [ 311 | [["t1", "t2", 101, '-100', { a => 11, b => 12, c => 13 }], { replace => 0, hash => 0 }, { 312 | count => 1, 313 | tuples => [ 314 | ['t1', 't2', 101, -100, { a => 11, b => 12, c => 13 }] 315 | ], 316 | status => 'ok', 317 | code => 0, 318 | sync => ignore(), 319 | schema_id => ignore(), 320 | }], 321 | [["t1", "t2", 17, '-100', { a => 11, b => 12, c => 13 }], { replace => 1, hash => 0 }, { 322 | count => 1, 323 | tuples => [ 324 | ['t1', 't2', 17, -100, { a => 11, b => 12, c => 13 }] 325 | ], 326 | status => 'ok', 327 | code => 0, 328 | sync => ignore(), 329 | schema_id => ignore(), 330 | }], 331 | [{_t1 => "t1", _t2 => "t2", _t3 => 18, _t4 => '-100', _t5 => "hello" }, { replace => 0, hash => 0 }, { 332 | count => 1, 333 | tuples => [ 334 | ['t1', 't2', 18, -100, 'hello'] 335 | ], 336 | status => 'ok', 337 | code => 0, 338 | sync => ignore(), 339 | schema_id => ignore(), 340 | }], 341 | 342 | # Not all fields supplied tests 343 | # You can't do this in 1.7.5 anymore 344 | ]; 345 | for my $p (@$_plan) { 346 | $c->insert($SPACE_NAME, $p->[0], $p->[1], sub { 347 | my $a = $_[0]; 348 | diag Dumper \@_ if !$a; 349 | cmp_deeply $a, $p->[2]; 350 | 351 | Renewer::renew_tnt($c, $SPACE_NAME, sub { 352 | EV::unloop; 353 | }); 354 | }); 355 | 356 | EV::loop; 357 | } 358 | }; 359 | 360 | subtest 'Replace tests', sub { 361 | plan( skip_all => 'skip') if !$test_exec{replace}; 362 | diag '==== Replace tests ====' if $ENV{TEST_VERBOSE}; 363 | 364 | my $_plan = [ 365 | [["t1", "t2", 101, '-100', { a => 11, b => 12, c => 13 }], { hash => 0 }, { 366 | count => 1, 367 | tuples => [ 368 | ['t1', 't2', 101, -100, { a => 11, b => 12, c => 13 }] 369 | ], 370 | status => 'ok', 371 | code => 0, 372 | sync => ignore(), 373 | schema_id => ignore(), 374 | }], 375 | [["t1", "t2", 17, '-100', { a => 11, b => 12, c => 13 }], { hash => 0 }, { 376 | count => 1, 377 | tuples => [ 378 | ['t1', 't2', 17, -100, { a => 11, b => 12, c => 13 }] 379 | ], 380 | status => 'ok', 381 | code => 0, 382 | sync => ignore(), 383 | schema_id => ignore(), 384 | }], 385 | [{_t1 => "t1", _t2 => "t2", _t3 => 18, _t4 => '-100' }, { hash => 0 }, { 386 | count => 1, 387 | tuples => [ 388 | ['t1', 't2', 18, -100, undef] 389 | ], 390 | status => 'ok', 391 | code => 0, 392 | sync => ignore(), 393 | schema_id => ignore(), 394 | }] 395 | ]; 396 | for my $p (@$_plan) { 397 | $c->replace($SPACE_NAME, $p->[0], $p->[1], sub { 398 | my $a = $_[0]; 399 | diag Dumper \@_ if !$a; 400 | cmp_deeply $a, $p->[2]; 401 | 402 | Renewer::renew_tnt($c, $SPACE_NAME, sub { 403 | EV::unloop; 404 | }); 405 | }); 406 | 407 | EV::loop; 408 | } 409 | }; 410 | 411 | 412 | subtest 'Delete tests', sub { 413 | plan( skip_all => 'skip') if !$test_exec{delete}; 414 | diag '==== Delete tests ====' if $ENV{TEST_VERBOSE}; 415 | 416 | my $_plan = [ 417 | [['tt1', 'tt2', 456], {}, { 418 | count => 1, 419 | tuples => [ 420 | { 421 | _t1 => 'tt1', 422 | _t2 => 'tt2', 423 | _t3 => 456, 424 | _t4 => 5, 425 | _t5 => 's' 426 | } 427 | ], 428 | status => 'ok', 429 | code => 0, 430 | sync => ignore(), 431 | schema_id => ignore(), 432 | }] 433 | ]; 434 | 435 | for my $p (@$_plan) { 436 | $c->delete($SPACE_NAME, $p->[0], $p->[1], sub { 437 | my $a = $_[0]; 438 | diag Dumper \@_ if !$a; 439 | cmp_deeply $a, $p->[2]; 440 | 441 | Renewer::renew_tnt($c, $SPACE_NAME, sub { 442 | EV::unloop; 443 | }); 444 | }); 445 | EV::loop; 446 | } 447 | 448 | 449 | }; 450 | 451 | subtest 'Update tests', sub { 452 | plan( skip_all => 'skip') if !$test_exec{update}; 453 | diag '==== Update tests ====' if $ENV{TEST_VERBOSE}; 454 | 455 | 456 | my $_plan = [ 457 | [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [3 => '+', 50] ], { hash => 1 }, { 458 | count => 1, 459 | tuples => [ 460 | { 461 | _t1 => 't1', 462 | _t2 => 't2', 463 | _t3 => 17, 464 | _t4 => -695, 465 | _t5 => 'heyo', 466 | } 467 | ], 468 | status => 'ok', 469 | code => 0, 470 | sync => ignore(), 471 | schema_id => ignore(), 472 | }], 473 | [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [3 => '+', -50] ], { hash => 0 }, { 474 | count => 1, 475 | tuples => [['t1', 't2', 17, -795, 'heyo']], 476 | status => 'ok', 477 | code => 0, 478 | sync => ignore(), 479 | schema_id => ignore(), 480 | }], 481 | [{_t1 => 'tt1',_t2 => 'tt2',_t3 => 456}, [ [3 => '&', 4] ], { hash => 0 }, { 482 | count => 1, 483 | tuples => [['tt1', 'tt2', 456, 4, 's']], 484 | status => 'ok', 485 | code => 0, 486 | sync => ignore(), 487 | schema_id => ignore(), 488 | }], 489 | [{_t1 => 'tt1',_t2 => 'tt2',_t3 => 456}, [ [3 => '^', 4] ], { hash => 0 }, { 490 | count => 1, 491 | tuples => [['tt1', 'tt2', 456, 1, 's']], 492 | status => 'ok', 493 | code => 0, 494 | sync => ignore(), 495 | schema_id => ignore(), 496 | }], 497 | [{_t1 => 'tt1',_t2 => 'tt2',_t3 => 456}, [ [3 => '|', 3] ], { hash => 0 }, { 498 | count => 1, 499 | tuples => [['tt1', 'tt2', 456, 7, 's']], 500 | status => 'ok', 501 | code => 0, 502 | sync => ignore(), 503 | schema_id => ignore(), 504 | }], 505 | # [{_t1 => 'tt1',_t2 => 'tt2',_t3 => 456}, [ [6 => '#', 1] ], { hash => 0 }, { 506 | # count => 1, 507 | # tuples => [['tt1', 'tt2', 456, 7, 's']], 508 | # status => 'ok', 509 | # code => 0, 510 | # sync => ignore(), 511 | # schema_id => ignore(), 512 | # }], 513 | [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [3 => '=', 12] ], { hash => 1 }, { 514 | count => 1, 515 | tuples => [ 516 | { 517 | _t1 => 't1', 518 | _t2 => 't2', 519 | _t3 => 17, 520 | _t4 => 12, 521 | _t5 => 'heyo', 522 | } 523 | ], 524 | status => 'ok', 525 | code => 0, 526 | sync => ignore(), 527 | schema_id => ignore(), 528 | }], 529 | [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [4 => '!', {a => 1, b => 2, c => 3}] ], { hash => 1 }, { 530 | count => 1, 531 | tuples => [ 532 | { 533 | _t1 => 't1', 534 | _t2 => 't2', 535 | _t3 => 17, 536 | _t4 => -745, 537 | _t5 => {a => 1, b => 2, c => 3}, 538 | '' => ['heyo'] 539 | } 540 | ], 541 | status => 'ok', 542 | code => 0, 543 | sync => ignore(), 544 | schema_id => ignore(), 545 | }], 546 | [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [4 => ':', 0, 3, 'romy'] ], { hash => 1 }, { 547 | count => 1, 548 | tuples => [ 549 | { 550 | _t5 => 'romyo', 551 | _t1 => 't1', 552 | _t2 => 't2', 553 | _t3 => 17, 554 | _t4 => -745, 555 | } 556 | ], 557 | status => 'ok', 558 | code => 0, 559 | sync => ignore(), 560 | schema_id => ignore(), 561 | }], 562 | [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [3 => '+', -50], [4 => '=', 'another_heyo'] ], { hash => 0 }, { 563 | count => 1, 564 | tuples => [['t1', 't2', 17, -795, 'another_heyo']], 565 | status => 'ok', 566 | code => 0, 567 | sync => ignore(), 568 | schema_id => ignore(), 569 | }] 570 | ]; 571 | 572 | for my $p (@$_plan) { 573 | $c->update($SPACE_NAME, $p->[0], $p->[1], $p->[2], sub { 574 | my $a = $_[0]; 575 | diag Dumper \@_ if !$a; 576 | 577 | cmp_deeply($a, $p->[3]); 578 | 579 | Renewer::renew_tnt($c, $SPACE_NAME,sub { 580 | EV::unloop; 581 | }); 582 | }); 583 | EV::loop; 584 | } 585 | }; 586 | 587 | 588 | subtest 'Upsert tests', sub { 589 | plan( skip_all => 'skip') if !$test_exec{upsert}; 590 | diag '==== Upsert tests ====' if $ENV{TEST_VERBOSE}; 591 | 592 | my $_plan = [ 593 | [{_t1 => 't1',_t2 => 't2',_t3 => 1, _t4 => 5, _t5 => 's'}, [ [3 => '=', 10] ], { hash => 0 }, { 594 | count => 1, 595 | tuples => [['t1', 't2', 1, 5, 's']], 596 | status => 'ok', 597 | code => 0, 598 | sync => ignore(), 599 | schema_id => ignore(), 600 | }], 601 | [{_t1 => 't1',_t2 => 't2',_t3 => 1, _t4 => 5, _t5 => 's'}, [ [3 => '=', 10] ], { hash => 0 }, { 602 | count => 1, 603 | tuples => [['t1', 't2', 1, 10, 's']], 604 | status => 'ok', 605 | code => 0, 606 | sync => ignore(), 607 | schema_id => ignore(), 608 | }], 609 | [{_t1 => 't1',_t2 => 't2',_t3 => 1, _t4 => 5, _t5 => 's'}, [ [3 => '+', 4] ], { hash => 0 }, { 610 | count => 1, 611 | tuples => [['t1', 't2', 1, 14, 's']], 612 | status => 'ok', 613 | code => 0, 614 | sync => ignore(), 615 | schema_id => ignore(), 616 | }], 617 | [{_t1 => 't1',_t2 => 't2',_t3 => 1, _t4 => 5, _t5 => 's'}, [ [3 => '-', 3] ], { hash => 0 }, { 618 | count => 1, 619 | tuples => [['t1', 't2', 1, 11, 's']], 620 | status => 'ok', 621 | code => 0, 622 | sync => ignore(), 623 | schema_id => ignore(), 624 | }], 625 | [{_t1 => 't1',_t2 => 't2',_t3 => 1, _t4 => 5, _t5 => 's'}, [ [3 => '=', 8] ], { hash => 0 }, { 626 | count => 1, 627 | tuples => [['t1', 't2', 1, 8, 's']], 628 | status => 'ok', 629 | code => 0, 630 | sync => ignore(), 631 | schema_id => ignore(), 632 | }], 633 | [{_t1 => 't1',_t2 => 't2',_t3 => 1, _t4 => 5, _t5 => 's'}, [ [4 => '=', 17] ], { hash => 0 }, { 634 | count => 1, 635 | tuples => [['t1', 't2', 1, 8, 17]], 636 | status => 'ok', 637 | code => 0, 638 | sync => ignore(), 639 | schema_id => ignore(), 640 | }], 641 | [{_t1 => 't1',_t2 => 't2',_t3 => 2, _t4 => 5, _t5 => 's'}, [ [3 => '=', 17] ], { hash => 0 }, { 642 | count => 2, 643 | tuples => [ 644 | ['t1', 't2', 1, 8, 17], 645 | ['t1', 't2', 2, 5, 's'], 646 | ], 647 | status => 'ok', 648 | code => 0, 649 | sync => ignore(), 650 | schema_id => ignore(), 651 | }], 652 | ]; 653 | 654 | Renewer::renew_tnt($c, $SPACE_NAME, 0, sub { 655 | EV::unloop; 656 | }); 657 | EV::loop; 658 | 659 | for my $p (@$_plan) { 660 | $c->upsert($SPACE_NAME, $p->[0], $p->[1], $p->[2], sub { 661 | $c->select($SPACE_NAME, [], { hash => 0 }, sub { 662 | my $a = $_[0]; 663 | # diag Dumper \@_;# if !$a; 664 | cmp_deeply($a, $p->[3]); 665 | EV::unloop; 666 | }); 667 | }); 668 | EV::loop; 669 | } 670 | 671 | Renewer::renew_tnt($c, $SPACE_NAME, 1, sub { 672 | EV::unloop; 673 | }); 674 | EV::loop; 675 | }; 676 | 677 | subtest 'RTREE tests', sub { 678 | plan( skip_all => 'skip') if !$test_exec{RTREE}; 679 | diag '==== RTREE tests ====' if $ENV{TEST_VERBOSE}; 680 | my $space = "rtree"; 681 | 682 | Renewer::renew_tnt($c, $space, sub { 683 | EV::unloop; 684 | }); 685 | EV::loop; 686 | 687 | 688 | my $_plan = [ 689 | ["select", [], {hash=>0}, { 690 | count => 0, 691 | tuples => [], 692 | status => 'ok', 693 | code => 0, 694 | sync => ignore(), 695 | schema_id => ignore(), 696 | }], 697 | ["insert", ['a1', [1,2]], {hash=>0}, { 698 | count => 1, 699 | tuples => [['a1', [1,2]]], 700 | status => 'ok', 701 | code => 0, 702 | sync => ignore(), 703 | schema_id => ignore(), 704 | }], 705 | ["insert", ['a2', [5,6,7,8]], {hash=>0}, { 706 | count => 1, 707 | tuples => [['a2', [5,6,7,8]]], 708 | status => 'ok', 709 | code => 0, 710 | sync => ignore(), 711 | schema_id => ignore(), 712 | }], 713 | ["insert", ['a3', [9,10,11,12]], {hash=>0}, { 714 | count => 1, 715 | tuples => [['a3', [9,10,11,12]]], 716 | status => 'ok', 717 | code => 0, 718 | sync => ignore(), 719 | schema_id => ignore(), 720 | }], 721 | ["insert", ['a4', [5,6,10,15]], {hash=>0}, { 722 | count => 1, 723 | tuples => [['a4', [5,6,10,15]]], 724 | status => 'ok', 725 | code => 0, 726 | sync => ignore(), 727 | schema_id => ignore(), 728 | }], 729 | ["select", ['a1'], {hash=>0}, { 730 | count => 1, 731 | tuples => [['a1', [1,2]]], 732 | status => 'ok', 733 | code => 0, 734 | sync => ignore(), 735 | schema_id => ignore(), 736 | }], 737 | ["select", ['a2'], {hash=>0}, { 738 | count => 1, 739 | tuples => [['a2', [5,6,7,8]]], 740 | status => 'ok', 741 | code => 0, 742 | sync => ignore(), 743 | schema_id => ignore(), 744 | }], 745 | ["select", ['a3'], {hash=>0}, { 746 | count => 1, 747 | tuples => [['a3', [9,10,11,12]]], 748 | status => 'ok', 749 | code => 0, 750 | sync => ignore(), 751 | schema_id => ignore(), 752 | }], 753 | ["select", ['a4'], {hash=>0}, { 754 | count => 1, 755 | tuples => [['a4', [5,6,10,15]]], 756 | status => 'ok', 757 | code => 0, 758 | sync => ignore(), 759 | schema_id => ignore(), 760 | }], 761 | ["select", [[5,6,7,8]], {hash=>0, index=>'spatial', iterator=>'EQ'}, { 762 | count => 1, 763 | tuples => [['a2', [5,6,7,8]]], 764 | status => 'ok', 765 | code => 0, 766 | sync => ignore(), 767 | schema_id => ignore(), 768 | }], 769 | ["select", [[5,6,7,8]], {hash=>0, index=>'spatial', iterator=>EV::Tarantool16::INDEX_OVERLAPS}, { 770 | count => 2, 771 | tuples => [['a2', [5,6,7,8]], ['a4', [5,6,10,15]]], 772 | status => 'ok', 773 | code => 0, 774 | sync => ignore(), 775 | schema_id => ignore(), 776 | }], 777 | ["select", [[5,6,7,8]], {hash=>0, index=>'spatial', iterator=>'GT'}, { 778 | count => 0, 779 | tuples => [], 780 | status => 'ok', 781 | code => 0, 782 | sync => ignore(), 783 | schema_id => ignore(), 784 | }], 785 | ["select", [[5,6,7,8]], {hash=>0, index=>'spatial', iterator=>EV::Tarantool16::INDEX_GE}, { 786 | count => 2, 787 | tuples => [['a2', [5,6,7,8]], ['a4', [5, 6, 10, 15]]], 788 | status => 'ok', 789 | code => 0, 790 | sync => ignore(), 791 | schema_id => ignore(), 792 | }], 793 | ["select", [[5,6,7,8]], {hash=>0, index=>'spatial', iterator=>EV::Tarantool16::INDEX_LT}, { 794 | count => 0, 795 | tuples => [], 796 | status => 'ok', 797 | code => 0, 798 | sync => ignore(), 799 | schema_id => ignore(), 800 | }], 801 | ]; 802 | 803 | for my $p (@$_plan) { 804 | my $op = $p->[0]; 805 | $c->$op($space, $p->[1], $p->[2], sub { 806 | my $a = $_[0]; 807 | diag Dumper \@_ if !$a; 808 | cmp_deeply $a, $p->[3]; 809 | EV::unloop; 810 | }); 811 | EV::loop; 812 | } 813 | 814 | 815 | }; 816 | 817 | done_testing() 818 | -------------------------------------------------------------------------------- /t/02-initial.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use EV; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use EV::Tarantool16; 8 | use Test::More; 9 | BEGIN{ $ENV{TEST_FAST} and plan 'skip_all'; } 10 | 11 | my $c = EV::Tarantool16->new({ 12 | host => 'localhost', 13 | port => 14032, 14 | reconnect => 1/3, 15 | connected => sub { 16 | fail "No call connected"; 17 | }, 18 | connfail => sub { 19 | fail "No call connfail"; 20 | }, 21 | disconnected => sub { 22 | fail "No call disconnected"; 23 | }, 24 | }); 25 | 26 | $EV::DIED = sub { 27 | diag "@_" if $ENV{TEST_VERBOSE}; 28 | EV::unloop; 29 | exit; 30 | }; 31 | 32 | is $c->state, 'INITIAL', 'Started from INITIAL'; 33 | 34 | # disconnect is a no-op 35 | $c->disconnect; 36 | $c->disconnect; 37 | $c->disconnect; 38 | 39 | $c->call('',[],sub { 40 | is_deeply \@_, [undef, "Not connected"], 'Call failed'; 41 | }); 42 | 43 | { 44 | local $SIG{ALRM} = sub { fail "loop locked"; exit; }; 45 | alarm 1; 46 | EV::loop; 47 | alarm 0; 48 | } 49 | 50 | $c->connect; 51 | is $c->state, 'RESOLVING', 'Switched to RESOLVING'; 52 | $c->connect for 1..10; 53 | $c->disconnect; 54 | is $c->state, 'DISCONNECTED'; 55 | 56 | $c->connect; 57 | is $c->state, 'RESOLVING'; 58 | $c->connect for 1..10; 59 | $c->disconnect; 60 | is $c->state, 'DISCONNECTED'; 61 | 62 | my $w;$w = EV::timer 1,0,sub { undef $w; fail "Timed out"; exit; }; 63 | 64 | $c->connect; 65 | EV::run( EV::RUN_ONCE ) 66 | while $c->state ne 'CONNECTING'; 67 | 68 | undef $w; 69 | 70 | 71 | is $c->state, 'CONNECTING'; 72 | $c->connect for 1..10; 73 | $c->disconnect; 74 | is $c->state, 'DISCONNECTED'; 75 | 76 | $w = EV::timer 1,0,sub { undef $w; fail "Timed out"; exit; }; 77 | 78 | $c->connect; 79 | EV::run( EV::RUN_ONCE ) 80 | while $c->state ne 'CONNECTING'; 81 | 82 | undef $w; 83 | 84 | is $c->state, 'CONNECTING'; 85 | 86 | diag "do reconnect" if $ENV{TEST_VERBOSE}; 87 | $c->reconnect; 88 | 89 | # Resolve is skipped on this step 90 | is $c->state, 'CONNECTING'; 91 | 92 | undef $c; 93 | done_testing; 94 | -------------------------------------------------------------------------------- /t/03-connected.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use EV; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use EV::Tarantool16; 8 | use Test::More; 9 | BEGIN{ $ENV{TEST_FAST} and plan 'skip_all'; } 10 | 11 | my $dis_call = 0; 12 | my $c = EV::Tarantool16->new({ 13 | host => 'localhost', 14 | port => 3301, 15 | reconnect => 0, 16 | log_level => $ENV{TEST_VERBOSE} ? 4 : 0, 17 | connected => sub { 18 | pass "Connected"; 19 | }, 20 | connfail => sub { 21 | fail "No call"; 22 | }, 23 | disconnected => sub { 24 | pass "Disconnected"; 25 | $dis_call = 1; 26 | }, 27 | }); 28 | 29 | is $c->state, 'INITIAL'; 30 | $c->connect; 31 | is $c->state, 'RESOLVING'; 32 | 33 | my $w;$w = EV::timer 1,0,sub { undef $w; fail "Timed out"; exit; }; 34 | 35 | $c->connect; 36 | EV::run( EV::RUN_ONCE ) 37 | while $c->state ne 'CONNECTING'; 38 | 39 | undef $w; 40 | 41 | if ($c->state eq 'CONNECTED') { 42 | $c->connect; 43 | $c->connect; 44 | $c->connect; 45 | 46 | is $dis_call, 0; 47 | 48 | $c->disconnect; 49 | 50 | is $dis_call, 1; 51 | 52 | is $c->state, 'DISCONNECTED'; 53 | } 54 | 55 | undef $c; 56 | done_testing; 57 | -------------------------------------------------------------------------------- /t/04-writeio.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use 5.010; 4 | use strict; 5 | use Test::More;# skip_all => "TODO"; 6 | BEGIN{ $ENV{TEST_FAST} and plan 'skip_all'; } 7 | use FindBin; 8 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 9 | use EV; 10 | use EV::Tarantool16; 11 | use Time::HiRes 'sleep','time'; 12 | use Data::Dumper; 13 | use Errno; 14 | use Scalar::Util 'weaken'; 15 | use Test::Tarantool16; 16 | 17 | $EV::DIED = sub { 18 | diag "@_" if $ENV{TEST_VERBOSE}; 19 | EV::unloop; 20 | exit; 21 | }; 22 | 23 | my $tnt = { 24 | name => 'tarantool_tester', 25 | port => 11723, 26 | host => '127.0.0.1', 27 | username => 'test_user', 28 | password => 'test_pass', 29 | initlua => do { 30 | my $file = 't/tnt/app.lua'; 31 | local $/ = undef; 32 | open my $f, "<", $file 33 | or die "could not open $file: $!"; 34 | my $d = <$f>; 35 | close $f; 36 | $d; 37 | } 38 | }; 39 | 40 | $tnt = Test::Tarantool16->new( 41 | title => $tnt->{name}, 42 | host => $tnt->{host}, 43 | port => $tnt->{port}, 44 | logger => sub { diag ( $tnt->{title},' ', @_ ) if $ENV{TEST_VERBOSE}; }, 45 | initlua => $tnt->{initlua}, 46 | wal_mode => 'write', 47 | on_die => sub { fail "tarantool $tnt->{name} is dead!: $!"; exit 1; }, 48 | ); 49 | 50 | $tnt->start(timeout => 10, sub { 51 | my ($status, $desc) = @_; 52 | if ($status == 1) { 53 | EV::unloop; 54 | } else { 55 | diag Dumper \@_; 56 | } 57 | }); 58 | EV::loop; 59 | 60 | my $w;$w = EV::timer 15,0,sub { undef $w; fail "Timed out"; exit; }; 61 | 62 | my $cfs = 0; 63 | my $c = EV::Tarantool16->new({ 64 | host => $tnt->{host}, 65 | port => $tnt->{port}, 66 | reconnect => 1, 67 | timeout => 10, 68 | log_level => $ENV{TEST_VERBOSE} ? 4 : 0, 69 | connected => sub { 70 | my $c = shift; 71 | my %start; 72 | 73 | $start{$c->sync + 1} = 1; 74 | $c->call('dummy',['x'x(2**20)], sub { 75 | if ($_[0]) { 76 | delete $start{ $_[0]{sync} }; 77 | pass "First big"; 78 | } else { 79 | fail "First big $_[1]"; 80 | } 81 | }); 82 | my $start_sync = $c->sync; 83 | for (1..2) { 84 | my $id = $start_sync + $_; 85 | $start{$id} = (); 86 | $c->call('dummy',['x', $_], sub { 87 | if ($_[0]) { 88 | if (exists $start{ $_[0]{sync} }) { 89 | delete $start{ $_[0]{sync} }; 90 | if (not %start) { 91 | pass "All done"; 92 | $c->disconnect; 93 | } 94 | } else { 95 | fail "Duplicate response for $_[0]{sync}"; 96 | EV::unloop; 97 | } 98 | } else { 99 | shift; 100 | fail "Request failed: @_"; 101 | EV::unloop; 102 | } 103 | }); 104 | } 105 | }, 106 | connfail => sub { 107 | my $err = 0+$!; 108 | is $err, Errno::ECONNREFUSED, 'connfail - refused' or diag "$!, $_[1]"; 109 | $cfs++ 110 | and 111 | EV::unloop; 112 | }, 113 | disconnected => sub { 114 | my $c = shift; 115 | diag "PL: Disconnected: @_" if $ENV{TEST_VERBOSE}; 116 | pass "Disconnected"; 117 | EV::unloop; 118 | }, 119 | }); 120 | 121 | $c->connect; 122 | 123 | EV::loop; 124 | done_testing(); 125 | -------------------------------------------------------------------------------- /t/05-auth.t: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | use 5.010; 4 | use strict; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use EV; 8 | use Time::HiRes 'sleep','time'; 9 | use Scalar::Util 'weaken'; 10 | use Errno; 11 | use EV::Tarantool16; 12 | use Test::More; 13 | BEGIN{ $ENV{TEST_FAST} and plan 'skip_all'; } 14 | use Test::Deep; 15 | use Data::Dumper; 16 | use Renewer; 17 | use Carp; 18 | 19 | is 1,1; 20 | 21 | done_testing(); 22 | -------------------------------------------------------------------------------- /t/06-timeout.t: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | use 5.010; 4 | use strict; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use EV; 8 | use Time::HiRes 'sleep','time'; 9 | use Scalar::Util 'weaken'; 10 | use Errno; 11 | use EV::Tarantool16; 12 | use Test::More; 13 | BEGIN{ $ENV{TEST_FAST} and plan 'skip_all'; } 14 | use Test::Deep; 15 | use Data::Dumper; 16 | use Renewer; 17 | use Carp; 18 | use Test::Tarantool16; 19 | 20 | my %test_exec = ( 21 | ping => 1, 22 | eval => 1, 23 | call => 1, 24 | select => 1, 25 | insert => 1, 26 | delete => 1, 27 | update => 1, 28 | upsert => 1, 29 | ); 30 | 31 | my $cfs = 0; 32 | my $connected; 33 | my $disconnected; 34 | 35 | my $w = AnyEvent->signal (signal => "INT", cb => sub { exit 0 }); 36 | 37 | my $tnt = { 38 | name => 'tarantool_tester', 39 | port => 11723, 40 | host => '127.0.0.1', 41 | username => 'test_user', 42 | password => 'test_pass', 43 | initlua => do { 44 | my $file = 't/tnt/app.lua'; 45 | local $/ = undef; 46 | open my $f, "<", $file 47 | or die "could not open $file: $!"; 48 | my $d = <$f>; 49 | close $f; 50 | $d; 51 | } 52 | }; 53 | 54 | $tnt = Test::Tarantool16->new( 55 | title => $tnt->{name}, 56 | host => $tnt->{host}, 57 | port => $tnt->{port}, 58 | logger => sub { diag ( $tnt->{title},' ', @_ ) if $ENV{TEST_VERBOSE}; }, 59 | initlua => $tnt->{initlua}, 60 | wal_mode => 'write', 61 | on_die => sub { fail "tarantool $tnt->{name} is dead!: $!"; exit 1; } 62 | ); 63 | 64 | $tnt->start(timeout => 10, sub { 65 | my ($status, $desc) = @_; 66 | if ($status == 1) { 67 | EV::unloop; 68 | } 69 | }); 70 | EV::loop; 71 | 72 | $tnt->{cnntrace} = 0; 73 | my $SPACE_NAME = 'tester'; 74 | 75 | 76 | my $c; $c = EV::Tarantool16->new({ 77 | host => $tnt->{host}, 78 | port => $tnt->{port}, 79 | username => $tnt->{username}, 80 | password => $tnt->{password}, 81 | reconnect => 0.2, 82 | cnntrace => $tnt->{cnntrace}, 83 | log_level => $ENV{TEST_VERBOSE} ? 4 : 0, 84 | connected => sub { 85 | diag Dumper \@_ unless $_[0]; 86 | diag "connected: @_" if $ENV{TEST_VERBOSE}; 87 | $connected++ if defined $_[0]; 88 | EV::unloop; 89 | }, 90 | connfail => sub { 91 | my $err = 0+$!; 92 | is $err, Errno::ECONNREFUSED, 'connfail - refused' or diag "$!, $_[1]"; 93 | # $nc->(@_) if $cfs == 0; 94 | $cfs++; 95 | # and 96 | EV::unloop; 97 | }, 98 | disconnected => sub { 99 | diag "discon: @_ / $!" if $ENV{TEST_VERBOSE}; 100 | $disconnected++; 101 | EV::unloop; 102 | }, 103 | }); 104 | 105 | $c->connect; 106 | EV::loop; 107 | 108 | ok $connected > 0, "Connection is ok"; 109 | croak "Not connected normally" unless $connected > 0; 110 | 111 | 112 | subtest 'Ping tests', sub { 113 | plan( skip_all => 'skip') if !$test_exec{ping}; 114 | diag '==== Ping timeout tests ===' if $ENV{TEST_VERBOSE}; 115 | 116 | my $f = sub { 117 | my ($opt, $cmp) = @_; 118 | $c->ping($opt, sub { 119 | cmp_deeply \@_, $cmp; 120 | EV::unloop; 121 | }); 122 | EV::loop; 123 | }; 124 | 125 | 126 | my $plan = [ 127 | [{timeout => 0.00001}, [ 128 | undef, 129 | "Request timed out" 130 | ]], 131 | [{}, [ 132 | { 133 | code => 0, 134 | sync => ignore(), 135 | schema_id => ignore(), 136 | } 137 | ]], 138 | [{timeout => 0.00001}, [ 139 | undef, 140 | "Request timed out" 141 | ]], 142 | [{timeout => 0.1}, [ 143 | { 144 | code => 0, 145 | sync => ignore(), 146 | schema_id => ignore(), 147 | } 148 | ]], 149 | ]; 150 | 151 | for my $p (@$plan) { 152 | $f->(@$p); 153 | } 154 | 155 | }; 156 | 157 | subtest 'Eval tests', sub { 158 | plan( skip_all => 'skip') if !$test_exec{eval}; 159 | diag '==== Eval timeout tests ===' if $ENV{TEST_VERBOSE}; 160 | 161 | my $f = sub { 162 | my ($opt, $cmp) = @_; 163 | $c->eval("return {box.info.status}", [], $opt, sub { 164 | cmp_deeply \@_, $cmp; 165 | EV::unloop; 166 | }); 167 | EV::loop; 168 | }; 169 | 170 | 171 | my $plan = [ 172 | [{timeout => 0.00001}, [ 173 | undef, 174 | "Request timed out" 175 | ]], 176 | [{}, [ 177 | { 178 | sync => ignore(), 179 | schema_id => ignore(), 180 | code => 0, 181 | status => "ok", 182 | count => ignore(), 183 | tuples => ignore() 184 | } 185 | ]], 186 | [{timeout => 0.00001}, [ 187 | undef, 188 | "Request timed out" 189 | ]], 190 | [{}, [ 191 | { 192 | sync => ignore(), 193 | schema_id => ignore(), 194 | code => 0, 195 | status => "ok", 196 | count => ignore(), 197 | tuples => ignore() 198 | } 199 | ]], 200 | ]; 201 | 202 | for my $p (@$plan) { 203 | $f->(@$p); 204 | } 205 | 206 | }; 207 | 208 | subtest 'Call tests', sub { 209 | plan( skip_all => 'skip') if !$test_exec{call}; 210 | diag '==== Call timeout tests ===' if $ENV{TEST_VERBOSE}; 211 | 212 | my $f = sub { 213 | my ($timeout, $opt, $cmp) = @_; 214 | $c->call("timeout_test", [$timeout], $opt, sub { 215 | cmp_deeply \@_, $cmp; 216 | EV::unloop; 217 | }); 218 | EV::loop; 219 | }; 220 | 221 | 222 | my $plan = [ 223 | [1.0, {timeout => 0.5}, [ 224 | undef, 225 | "Request timed out" 226 | ]], 227 | [0.5, {}, [ 228 | { 229 | sync => ignore(), 230 | schema_id => ignore(), 231 | code => 0, 232 | status => "ok", 233 | count => ignore(), 234 | tuples => ignore() 235 | } 236 | ]], 237 | [0.5, {timeout => 0.2}, [ 238 | undef, 239 | "Request timed out" 240 | ]], 241 | [1.0, {timeout => 2.0}, [ 242 | { 243 | sync => ignore(), 244 | schema_id => ignore(), 245 | code => 0, 246 | status => "ok", 247 | count => ignore(), 248 | tuples => ignore() 249 | } 250 | ]], 251 | ]; 252 | 253 | for my $p (@$plan) { 254 | $f->(@$p); 255 | } 256 | 257 | }; 258 | 259 | 260 | subtest 'Select tests', sub { 261 | plan( skip_all => 'skip') if !$test_exec{ping}; 262 | diag '==== Select timeout tests ===' if $ENV{TEST_VERBOSE}; 263 | 264 | my $f = sub { 265 | my ($key, $opt, $cmp) = @_; 266 | $c->select($SPACE_NAME, $key, $opt, sub { 267 | cmp_deeply \@_, $cmp; 268 | EV::unloop; 269 | }); 270 | EV::loop; 271 | }; 272 | 273 | 274 | my $plan = [ 275 | [[], {timeout => 0.00001}, [ 276 | undef, 277 | "Request timed out" 278 | ]], 279 | [{}, {}, [ 280 | { 281 | sync => ignore(), 282 | schema_id => ignore(), 283 | code => 0, 284 | status => "ok", 285 | count => ignore(), 286 | tuples => ignore() 287 | } 288 | ]], 289 | [[], {timeout => 0.00001}, [ 290 | undef, 291 | "Request timed out" 292 | ]], 293 | [{}, {timeout => 0.1}, [ 294 | { 295 | sync => ignore(), 296 | schema_id => ignore(), 297 | code => 0, 298 | status => "ok", 299 | count => ignore(), 300 | tuples => ignore() 301 | } 302 | ]], 303 | ]; 304 | 305 | for my $p (@$plan) { 306 | $f->(@$p); 307 | } 308 | 309 | }; 310 | 311 | 312 | subtest 'Insert tests', sub { 313 | plan( skip_all => 'skip') if !$test_exec{insert}; 314 | diag '==== Insert timeout tests ===' if $ENV{TEST_VERBOSE}; 315 | 316 | my $f = sub { 317 | my ($args, $opt, $cmp) = @_; 318 | $c->insert($SPACE_NAME, $args, $opt, sub { 319 | cmp_deeply \@_, $cmp; 320 | EV::unloop; 321 | }); 322 | EV::loop; 323 | }; 324 | 325 | 326 | my $plan = [ 327 | [{_t1 => "t1", _t2 => "t2", _t3 => 180, _t4 => '-100', _t5 => 's' }, {timeout => 0.00001}, [ 328 | undef, 329 | "Request timed out" 330 | ]], 331 | [{_t1 => "t1", _t2 => "t2", _t3 => 181, _t4 => '-100', _t5 => 's' }, {}, [ 332 | { 333 | sync => ignore(), 334 | schema_id => ignore(), 335 | code => 0, 336 | status => "ok", 337 | count => ignore(), 338 | tuples => ignore() 339 | } 340 | ]], 341 | [{_t1 => "t1", _t2 => "t2", _t3 => 182, _t4 => '-100', _t5 => 's' }, {timeout => 0.00001}, [ 342 | undef, 343 | "Request timed out" 344 | ]], 345 | [{_t1 => "t1", _t2 => "t2", _t3 => 183, _t4 => '-100', _t5 => 's' }, {}, [ 346 | { 347 | sync => ignore(), 348 | schema_id => ignore(), 349 | code => 0, 350 | status => "ok", 351 | count => ignore(), 352 | tuples => ignore() 353 | } 354 | ]], 355 | ]; 356 | 357 | for my $p (@$plan) { 358 | $f->(@$p); 359 | } 360 | 361 | }; 362 | 363 | subtest 'Delete tests', sub { 364 | plan( skip_all => 'skip') if !$test_exec{delete}; 365 | diag '==== Delete timeout tests ===' if $ENV{TEST_VERBOSE}; 366 | 367 | my $f = sub { 368 | my ($args, $opt, $cmp) = @_; 369 | $c->delete($SPACE_NAME, $args, $opt, sub { 370 | cmp_deeply \@_, $cmp; 371 | EV::unloop; 372 | }); 373 | EV::loop; 374 | }; 375 | 376 | 377 | my $plan = [ 378 | [{_t1 => "t1", _t2 => "t2", _t3 => 180 }, {timeout => 0.00001}, [ 379 | undef, 380 | "Request timed out" 381 | ]], 382 | [{_t1 => "t1", _t2 => "t2", _t3 => 181 }, {}, [ 383 | { 384 | sync => ignore(), 385 | schema_id => ignore(), 386 | code => 0, 387 | status => "ok", 388 | count => ignore(), 389 | tuples => ignore() 390 | } 391 | ]], 392 | [{_t1 => "t1", _t2 => "t2", _t3 => 182 }, {timeout => 0.00001}, [ 393 | undef, 394 | "Request timed out" 395 | ]], 396 | [{_t1 => "t1", _t2 => "t2", _t3 => 183 }, {}, [ 397 | { 398 | sync => ignore(), 399 | schema_id => ignore(), 400 | code => 0, 401 | status => "ok", 402 | count => ignore(), 403 | tuples => ignore() 404 | } 405 | ]], 406 | ]; 407 | 408 | for my $p (@$plan) { 409 | $f->(@$p); 410 | } 411 | 412 | }; 413 | 414 | subtest 'Update tests', sub { 415 | plan( skip_all => 'skip') if !$test_exec{update}; 416 | diag '==== Update timeout tests ===' if $ENV{TEST_VERBOSE}; 417 | 418 | my $f = sub { 419 | my ($key, $tuples, $opt, $cmp) = @_; 420 | $c->update($SPACE_NAME, $key, $tuples, $opt, sub { 421 | cmp_deeply \@_, $cmp; 422 | EV::unloop; 423 | }); 424 | EV::loop; 425 | }; 426 | 427 | 428 | my $plan = [ 429 | [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [3 => '+', 50] ], {timeout => 0.00001}, [ 430 | undef, 431 | "Request timed out" 432 | ]], 433 | [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [3 => '+', 50] ], {}, [ 434 | { 435 | sync => ignore(), 436 | schema_id => ignore(), 437 | code => 0, 438 | status => "ok", 439 | count => ignore(), 440 | tuples => ignore() 441 | } 442 | ]], 443 | [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [3 => '+', 50] ], {timeout => 0.00001}, [ 444 | undef, 445 | "Request timed out" 446 | ]], 447 | [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [3 => '+', 50] ], {}, [ 448 | { 449 | sync => ignore(), 450 | schema_id => ignore(), 451 | code => 0, 452 | status => "ok", 453 | count => ignore(), 454 | tuples => ignore(), 455 | } 456 | ]], 457 | ]; 458 | 459 | for my $p (@$plan) { 460 | $f->(@$p); 461 | } 462 | 463 | }; 464 | 465 | 466 | subtest 'Upsert tests', sub { 467 | plan( skip_all => 'skip') if !$test_exec{update}; 468 | diag '==== Upsert timeout tests ===' if $ENV{TEST_VERBOSE}; 469 | 470 | my $f = sub { 471 | my ($tuple, $operations, $opt, $cmp) = @_; 472 | $c->upsert($SPACE_NAME, $tuple, $operations, $opt, sub { 473 | cmp_deeply \@_, $cmp; 474 | EV::unloop; 475 | }); 476 | EV::loop; 477 | }; 478 | 479 | 480 | my $plan = [ 481 | [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [3 => '+', 50] ], {timeout => 0.00001}, [ 482 | undef, 483 | "Request timed out" 484 | ]], 485 | [{_t1 => 't1',_t2 => 't2',_t3 => 17, _t4 => 20, _t5 => 's'}, [ [3 => '=', 50] ], {}, [ 486 | { 487 | sync => ignore(), 488 | schema_id => ignore(), 489 | code => 0, 490 | status => "ok", 491 | count => ignore(), 492 | tuples => ignore() 493 | } 494 | ]], 495 | [{_t1 => 't1',_t2 => 't2',_t3 => 17, _t4 => 20, _t5 => 's'}, [ [3 => '=', 50] ], {timeout => 0.00001}, [ 496 | undef, 497 | "Request timed out" 498 | ]], 499 | [{_t1 => 't1',_t2 => 't2',_t3 => 17, _t4 => 20, _t5 => 's'}, [ [3 => '=', 50] ], {}, [ 500 | { 501 | sync => ignore(), 502 | schema_id => ignore(), 503 | code => 0, 504 | status => "ok", 505 | count => ignore(), 506 | tuples => ignore(), 507 | } 508 | ]], 509 | ]; 510 | 511 | Renewer::renew_tnt($c, $SPACE_NAME, 0, sub { 512 | EV::unloop; 513 | }); 514 | EV::loop; 515 | 516 | for my $p (@$plan) { 517 | $f->(@$p); 518 | } 519 | 520 | }; 521 | 522 | 523 | done_testing(); 524 | -------------------------------------------------------------------------------- /t/07-memory.t: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | use 5.010; 4 | use strict; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use EV; 8 | use Time::HiRes 'sleep','time'; 9 | use Scalar::Util 'weaken'; 10 | use Errno; 11 | use EV::Tarantool16; 12 | use Test::More; 13 | BEGIN{ $ENV{TEST_FAST} || $ENV{TEST_FAST_MEM} and plan 'skip_all'; } 14 | use Test::Deep; 15 | use Data::Dumper; 16 | use Renewer; 17 | use Carp; 18 | use Test::Tarantool16; 19 | use Proc::ProcessTable; 20 | 21 | sub find_self_proc { 22 | my $t = Proc::ProcessTable->new(); 23 | my $proc; 24 | for my $p ( @{$t->table} ){ 25 | if ($p->pid == $$) { 26 | $proc = $p; 27 | last; 28 | } 29 | } 30 | if (!$proc) { 31 | die "Couldn't find self process in ProcessTable"; 32 | } 33 | return $proc; 34 | }; 35 | 36 | 37 | my $w = AnyEvent->signal (signal => "INT", cb => sub { exit 0 }); 38 | 39 | my $tnt = { 40 | name => 'tarantool_tester', 41 | port => 11723, 42 | host => '127.0.0.1', 43 | username => 'test_user', 44 | password => 'test_pass', 45 | initlua => do { 46 | my $file = 't/tnt/app.lua'; 47 | local $/ = undef; 48 | open my $f, "<", $file 49 | or die "could not open $file: $!"; 50 | my $d = <$f>; 51 | close $f; 52 | $d; 53 | } 54 | }; 55 | 56 | $tnt = Test::Tarantool16->new( 57 | title => $tnt->{name}, 58 | host => $tnt->{host}, 59 | port => $tnt->{port}, 60 | logger => sub { diag ( $tnt->{title},' ', @_ ) if $ENV{TEST_VERBOSE}; }, 61 | initlua => $tnt->{initlua}, 62 | wal_mode => 'write', 63 | on_die => sub { fail "tarantool $tnt->{name} is dead!: $!"; exit 1; } 64 | ); 65 | 66 | $tnt->start(timeout => 10, sub { 67 | my ($status, $desc) = @_; 68 | if ($status == 1) { 69 | EV::unloop; 70 | } 71 | }); 72 | EV::loop; 73 | 74 | $tnt->{cnntrace} = 0; 75 | my $SPACE_NAME = 'tester'; 76 | 77 | my $c; 78 | 79 | sub meminfo () { 80 | my $proc = find_self_proc(); 81 | my %s = ( 82 | vsize => $proc->size, 83 | rss => $proc->rss, 84 | ); 85 | return (@s{qw(rss vsize)}); 86 | } 87 | 88 | sub memcheck ($$$$) { 89 | my ($n,$obj,$method,$args) = @_; 90 | my ($rss1,$vsz1) = meminfo(); 91 | my $cnt = 0; 92 | my $start = time; 93 | my $do;$do = sub { 94 | return EV::unloop if ++$cnt >= $n; 95 | $obj->$method(@$args,$do); 96 | };$do->(); 97 | EV::loop; 98 | my $run = time - $start; 99 | my ($rss2,$vsz2) = meminfo(); 100 | diag sprintf "$method: %0.6fs/%d; %0.2f rps (%+0.2fk/%+0.2fk)",$run,$cnt, $cnt/$run, ($rss2-$rss1)/1024, ($vsz2 - $vsz1)/1024; 101 | if ($rss2 > $rss1 or $vsz2 > $vsz1) { 102 | diag sprintf "%0.2fM/%0.2fM -> %0.2fM/%0.2fM", $rss1/1024/1024,$vsz1/1024/1024, $rss2/1024/1024,$vsz2/1024/1024; 103 | } 104 | is 1, 1; 105 | } 106 | 107 | 108 | diag '==== Memory tests ====' if $ENV{TEST_VERBOSE}; 109 | 110 | subtest 'connect/disconnect test', sub { 111 | # plan( skip_all => 'skip'); 112 | 113 | for (0..5) { 114 | my $cnt = 0; 115 | my $start = time; 116 | my $max_cnt = 10000; 117 | undef $c; 118 | 119 | my ($rss1,$vsz1) = meminfo(); 120 | 121 | $c = EV::Tarantool16->new({ 122 | host => $tnt->{host}, 123 | port => $tnt->{port}, 124 | username => $tnt->{username}, 125 | password => $tnt->{password}, 126 | reconnect => 0.2, 127 | cnntrace => $tnt->{cnntrace}, 128 | log_level => $ENV{TEST_VERBOSE} ? 1 : 0, 129 | connected => sub { 130 | my $c = shift; 131 | diag Dumper \@_ unless $_[0]; 132 | $c->disconnect; 133 | return EV::unloop if ++$cnt >= $max_cnt; 134 | $c->connect; 135 | }, 136 | connfail => sub { 137 | my $c = shift; 138 | diag "@_ / $!"; 139 | EV::unloop; 140 | }, 141 | disconnected => sub { 142 | }, 143 | }); 144 | 145 | $c->connect; 146 | EV::loop; 147 | undef $c; 148 | 149 | my $run = time - $start; 150 | my ($rss2,$vsz2) = meminfo(); 151 | diag sprintf "connect/disconnect: %0.6fs/%d; %0.2f rps (%+0.2fk/%+0.2fk)",$run,$cnt, $cnt/$run, ($rss2-$rss1)/1024, ($vsz2 - $vsz1)/1024; 152 | if ($rss2 > $rss1 or $vsz2 > $vsz1) { 153 | diag sprintf "%0.2fM/%0.2fM -> %0.2fM/%0.2fM", $rss1/1024/1024,$vsz1/1024/1024, $rss2/1024/1024,$vsz2/1024/1024; 154 | } 155 | } 156 | is 1, 1; 157 | }; 158 | 159 | subtest 'basic memory test', sub { 160 | $c = EV::Tarantool16->new({ 161 | host => $tnt->{host}, 162 | port => $tnt->{port}, 163 | username => $tnt->{username}, 164 | password => $tnt->{password}, 165 | reconnect => 0.2, 166 | cnntrace => $tnt->{cnntrace}, 167 | log_level => $ENV{TEST_VERBOSE} ? 1 : 0, 168 | connected => sub { 169 | EV::unloop; 170 | }, 171 | connfail => sub { 172 | my $c = shift; 173 | diag "@_ / $!"; 174 | EV::unloop; 175 | }, 176 | disconnected => sub { 177 | diag "@_ / $!" if $ENV{TEST_VERBOSE}; 178 | EV::unloop; 179 | }, 180 | }); 181 | 182 | $c->connect; 183 | EV::loop; 184 | 185 | 186 | memcheck 50000, $c, "ping",[]; 187 | memcheck 50000, $c, "eval",["return {'hey'}", []]; 188 | memcheck 50000, $c, "call",["string_function",[]]; 189 | memcheck 50000, $c, "lua",["string_function",[]]; 190 | memcheck 50000, $c, "select",[$SPACE_NAME,{ _t1 => 't1' }]; 191 | memcheck 50000, $c, "replace",[$SPACE_NAME,['t1', 't2', 12, 100 ], { hash => 1}]; 192 | memcheck 50000, $c, "update",[$SPACE_NAME,{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [3 => '+', 1] ], { hash => 1 }]; 193 | memcheck 50000, $c, "upsert",[$SPACE_NAME,{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [3 => '=', 1] ], { hash => 1 }]; 194 | memcheck 50000, $c, "eval",["return {box.info}", [], { timeout => 0.00001 }]; 195 | }; 196 | 197 | done_testing; 198 | -------------------------------------------------------------------------------- /t/08-multi.t: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | use 5.010; 4 | use strict; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use EV; 8 | use Time::HiRes 'sleep','time'; 9 | use Scalar::Util 'weaken'; 10 | use Errno; 11 | use EV::Tarantool16; 12 | use EV::Tarantool16::Multi; 13 | use Test::More; 14 | BEGIN{ $ENV{TEST_FAST} and plan 'skip_all'; } 15 | use Test::Deep; 16 | use Data::Dumper; 17 | use Carp; 18 | use Test::Tarantool16; 19 | # use Devel::Leak; 20 | use AE; 21 | 22 | $EV::DIED = sub { 23 | diag "@_" if $ENV{TEST_VERBOSE}; 24 | EV::unloop; 25 | exit; 26 | }; 27 | 28 | my %test_exec = ( 29 | ping => 1, 30 | eval => 1, 31 | call => 1, 32 | lua => 1, 33 | select => 1, 34 | insert => 1, 35 | replace => 1, 36 | delete => 1, 37 | update => 1, 38 | upsert => 1, 39 | RTREE => 1, 40 | # memtest => 0 41 | ); 42 | 43 | my $cfs = 0; 44 | my $connected; 45 | my $disconnected; 46 | 47 | my $w = AnyEvent->signal (signal => "INT", cb => sub { exit 0 }); 48 | 49 | my $port = 11723; 50 | my @required_tnts = ( 51 | '127.0.0.1', 52 | '127.0.0.2', 53 | ); 54 | 55 | my @tnts = map { { 56 | name => 'tarantool_tester['.$_.']', 57 | port => $port, 58 | host => $_, 59 | username => 'test_user', 60 | password => 'test_pass', 61 | initlua => do { 62 | my $file = 't/tnt/app.lua'; 63 | local $/ = undef; 64 | open my $f, "<", $file 65 | or die "could not open $file: $!"; 66 | my $d = <$f>; 67 | close $f; 68 | $d; 69 | } 70 | } } @required_tnts; 71 | 72 | 73 | @tnts = map { my $tnt = $_; Test::Tarantool16->new( 74 | title => $tnt->{name}, 75 | host => $tnt->{host}, 76 | port => $tnt->{port}, 77 | logger => sub { diag ( $tnt->{name},' ', @_ ) if $ENV{TEST_VERBOSE};}, 78 | initlua => $tnt->{initlua}, 79 | wal_mode => 'write', 80 | on_die => sub { fail "tarantool $tnt->{name} is dead!: $!"; exit 1; }, 81 | ) } @tnts; 82 | 83 | for (@tnts) { 84 | $_->start(timeout => 10, sub { 85 | my ($status, $desc) = @_; 86 | if ($status == 1) { 87 | EV::unloop; 88 | } else { 89 | diag Dumper \@_ if $ENV{TEST_VERBOSE}; 90 | } 91 | }); 92 | EV::loop; 93 | 94 | my $w; $w = EV::timer 1, 0, sub { 95 | undef $w; 96 | EV::unloop; 97 | }; 98 | EV::loop; 99 | } 100 | 101 | my $timeout = 5; 102 | my $w_timeout; $w_timeout = AE::timer $timeout, 0, sub { 103 | undef $w_timeout; 104 | 105 | fail "Couldn't connect to Multi in $timeout seconds"; 106 | EV::unloop; 107 | }; 108 | 109 | my $c; $c = EV::Tarantool16::Multi->new( 110 | cnntrace => 0, 111 | reconnect => 0.2, 112 | log_level => $ENV{TEST_VERBOSE} ? 4 : 0, 113 | servers => [ 114 | "127.0.0.1:$port", 115 | "127.0.0.2:$port", 116 | ], 117 | connected => sub { 118 | diag Dumper \@_ unless $_[0]; 119 | diag "connected: @_" if $ENV{TEST_VERBOSE}; 120 | }, 121 | one_connected => sub { 122 | diag Dumper \@_ unless $_[0]; 123 | $connected++ if defined $_[0]; 124 | }, 125 | connfail => sub { 126 | my $err = 0+$!; 127 | is $err, Errno::ECONNREFUSED, 'connfail - refused' or diag "$!, $_[1]"; 128 | # $nc->(@_) if $cfs == 0; 129 | $cfs++; 130 | # and 131 | EV::unloop; 132 | }, 133 | disconnected => sub { 134 | diag "discon: @_ / $!" if $ENV{TEST_VERBOSE}; 135 | $disconnected++; 136 | }, 137 | all_connected => sub { 138 | undef $w_timeout; 139 | diag Dumper \@_ unless $_[0]; 140 | diag "all_connected: @_" if $ENV{TEST_VERBOSE}; 141 | is $connected, scalar @required_tnts, 'Connected to correct number of nodes'; 142 | EV::unloop; 143 | }, 144 | all_disconnected => sub { 145 | diag Dumper \@_ unless $_[0]; 146 | diag "all_disconnected: @_" if $ENV{TEST_VERBOSE}; 147 | EV::unloop; 148 | } 149 | ); 150 | 151 | $c->connect; 152 | EV::loop; 153 | 154 | for (1..100) { 155 | $c->ping(sub { 156 | isnt shift, undef, 'ping request successful'; 157 | EV::unloop; 158 | }); 159 | EV::loop; 160 | } 161 | done_testing; 162 | -------------------------------------------------------------------------------- /t/lib/Renewer.pm: -------------------------------------------------------------------------------- 1 | package Renewer; 2 | 3 | use 5.010; 4 | use strict; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use EV; 8 | use Data::Dumper; 9 | use Errno; 10 | use Test::More; 11 | 12 | 13 | sub renew_tnt { 14 | my $c = shift; 15 | my $space = shift; 16 | my $cb = pop; 17 | my $do_fill = shift // 1; 18 | 19 | $c->call("truncate_$space", [], sub { 20 | if ($do_fill) { 21 | $c->call("fill_$space", [], sub { 22 | # my $a = $_[0]; 23 | # diag Dumper $a; 24 | $cb->(); 25 | }); 26 | } else { 27 | $cb->(); 28 | } 29 | }); 30 | } 31 | 32 | 1; 33 | -------------------------------------------------------------------------------- /t/tnt/app.lua: -------------------------------------------------------------------------------- 1 | local log = require('log') 2 | local yaml = require('yaml') 3 | 4 | local username = 'test_user' 5 | local password = 'test_pass' 6 | 7 | if #box.space._user.index.name:select({username}) ~= 0 then 8 | box.schema.user.drop(username) 9 | end 10 | box.schema.user.create('test_user', {password=password, if_not_exists=true}) 11 | box.schema.user.grant('test_user','read,write,execute,create,drop','universe') 12 | 13 | local function bootstrap() 14 | local b = { 15 | tarantool_ver = box.info.version, 16 | has_new_types = false, 17 | types = {} 18 | } 19 | 20 | if b.tarantool_ver >= "1.7.1-245" then 21 | b.has_new_types = true 22 | b.types.string = 'string' 23 | b.types.unsigned = 'unsigned' 24 | b.types.integer = 'integer' 25 | else 26 | b.types.string = 'str' 27 | b.types.unsigned = 'num' 28 | b.types.integer = 'int' 29 | end 30 | b.types.number = 'number' 31 | b.types.array = 'array' 32 | b.types.scalar = 'scalar' 33 | b.types.any = '*' 34 | return b 35 | end 36 | 37 | local B = bootstrap() 38 | log.info(yaml.encode(B)) 39 | 40 | s_tester = box.space.tester 41 | if s_tester then 42 | s_tester:drop{} 43 | end 44 | 45 | s_rtree = box.space.rtree 46 | if s_rtree then 47 | s_rtree:drop{} 48 | end 49 | 50 | s_memier = box.space.memier 51 | if s_memier then 52 | s_memier:drop{} 53 | end 54 | 55 | s_vinyl = box.space.vinyler 56 | if s_vinyl then 57 | s_vinyl:drop{} 58 | end 59 | 60 | 61 | ------------------------------------------------------------------------------- 62 | 63 | local function init_tester(s) 64 | _format = { 65 | {type=B.types.string, name='_t1'}, 66 | {type=B.types.string, name='_t2'}, 67 | {type=B.types.unsigned, name='_t3'}, 68 | {type=B.types.number, name='_t4'}, 69 | {type=B.types.any, name='_t5'}, 70 | } 71 | s:format(_format) 72 | 73 | i = s:create_index('primary', {type = 'tree', parts = {1, B.types.string, 2, B.types.string, 3, B.types.unsigned}}) 74 | i = s:create_index('tt', {type = 'tree', unique = false, parts = { 4, 'number' } }) 75 | -- box.space.tester:insert({'s','a',3,4}) 76 | end 77 | 78 | function res() 79 | return box.space.tester:select{} 80 | end 81 | 82 | function fill_tester() 83 | arr = {1, 2, 3, "str1", 4} 84 | obj = {} 85 | obj['key1'] = "value1" 86 | obj['key2'] = 42 87 | obj[33] = true 88 | obj[35] = false 89 | 90 | box.space.tester:insert{"t1", "t2", 17, -745, "heyo"}; 91 | box.space.tester:insert{"t1", "t2", 2, 200, arr}; 92 | box.space.tester:insert{"t1", "t2", 3, 0, obj}; 93 | box.space.tester:insert{"tt1", "tt2", 456, 5, "s"}; 94 | end 95 | 96 | function truncate_tester() 97 | s = box.space.tester 98 | t = {} 99 | i = 1 100 | for k, v in s:pairs() do 101 | t[i] = {v[1], v[2], v[3]} 102 | i = i + 1 103 | end 104 | 105 | for i, v in pairs(t) do 106 | s:delete(v) 107 | end 108 | end 109 | 110 | ------------------------------------------------------------------------------- 111 | 112 | local function init_vinyler(s) 113 | i = s:create_index('primary', {type = 'tree', parts = {1, B.types.string}}) 114 | end 115 | 116 | local function _truncate_vinyler(s) 117 | t = {} 118 | i = 1 119 | for k, v in s:pairs() do 120 | t[i] = {v[1]} 121 | i = i + 1 122 | end 123 | 124 | for i, v in pairs(t) do 125 | s:delete(v) 126 | end 127 | end 128 | 129 | function truncate_vinyler() 130 | s = box.space.vinyler 131 | _truncate_vinyler(s) 132 | end 133 | 134 | function truncate_memier() 135 | s = box.space.memier 136 | _truncate_vinyler(s) 137 | end 138 | 139 | 140 | ------------------------------------------------------------------------------- 141 | 142 | 143 | local function init_rtree(s) 144 | i = s:create_index('primary', {type = 'TREE', parts = {1, B.types.string}}) 145 | i2 = s:create_index('spatial', {type = 'RTREE', unique = false, parts = {2, B.types.array}}) 146 | end 147 | 148 | function fill_rtree() 149 | 150 | end 151 | 152 | function truncate_rtree() 153 | s = box.space.rtree 154 | t = {} 155 | i = 1 156 | for k, v in s:pairs() do 157 | t[i] = {v[1]} 158 | i = i + 1 159 | end 160 | 161 | for i, v in pairs(t) do 162 | s:delete(v) 163 | end 164 | end 165 | 166 | 167 | ------------------------------------------------------------------------------- 168 | 169 | 170 | s_tester = box.schema.space.create('tester') 171 | init_tester(s_tester) 172 | 173 | s_memier = box.schema.space.create('memier', {engine = 'memtx'}) 174 | init_vinyler(s_memier) 175 | 176 | s_vinyl = box.schema.space.create('vinyler', {engine = 'vinyl'}) 177 | init_vinyler(s_vinyl) 178 | 179 | s_rtree = box.schema.space.create('rtree') 180 | init_rtree(s_rtree) 181 | 182 | 183 | function string_function() 184 | return "hello world" 185 | end 186 | 187 | function timeout_test(timeout) 188 | local fiber = require('fiber') 189 | local ch = fiber.channel(1) 190 | ch:get(timeout) 191 | return 'ok' 192 | end 193 | 194 | function truncate_all() 195 | print "Truncating tester space" 196 | truncate_tester() 197 | 198 | print "Truncating vinyler space" 199 | truncate_vinyler() 200 | 201 | print "Truncating memier space" 202 | truncate_memier() 203 | 204 | print "Truncating rtree space" 205 | truncate_rtree() 206 | end 207 | 208 | 209 | function get_test_tuple() 210 | local t = box.space.tester:select{}[1] 211 | return t 212 | end 213 | 214 | 215 | 216 | function dummy(arg) 217 | return 'ok' 218 | end 219 | -------------------------------------------------------------------------------- /t/tnt/init.lua: -------------------------------------------------------------------------------- 1 | box.cfg{ 2 | listen = "127.0.0.1:3301", 3 | work_dir = ".", 4 | -- wal_mode = "none" 5 | } 6 | 7 | box.schema.user.grant('guest','read,write,execute','universe') 8 | 9 | require('console').listen('127.0.0.1:3302') 10 | 11 | local function script_path() local fio = require('fio');local b = debug.getinfo(2, "S").source:sub(2);local lb = fio.readlink(b);if lb ~= nil then b = lb end;return b:match("(.*/)") end 12 | dofile(script_path() .. '/app.lua') 13 | require('console').start() 14 | 15 | -------------------------------------------------------------------------------- /temp/dbg.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use 5.010; 3 | use FindBin; 4 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 5 | use EV::Tarantool16; 6 | use Data::Dumper; 7 | 8 | sub runner { 9 | my ($n,$obj,$method,$args) = @_; 10 | my $cnt = 0; 11 | my $do;$do = sub { 12 | # warn "[$cnt/$n] call $method(@$args): @_"; 13 | # diag Dumper \@_; 14 | return EV::unloop if ++$cnt >= $n; 15 | $obj->$method(@$args,$do); 16 | };$do->(); 17 | EV::loop; 18 | } 19 | 20 | my $c; $c = EV::Tarantool16->new({ 21 | host => '127.0.0.1', 22 | port => 3301, 23 | username => 'test_user', 24 | password => 'test_pass', 25 | reconnect => 0.2, 26 | timeout => 0, 27 | connected => sub { 28 | warn "connected: @_"; 29 | EV::unloop; 30 | }, 31 | connfail => sub { 32 | warn "connfail: @_ / $!"; 33 | EV::unloop; 34 | }, 35 | disconnected => sub { 36 | warn "discon: @_ / $!"; 37 | EV::unloop; 38 | }, 39 | }); 40 | 41 | $c->connect; 42 | EV::loop; 43 | 44 | # $c->ping(sub { 45 | # my $a = @_[0]; 46 | # warn Dumper \@_ if !$a; 47 | # EV::unloop; 48 | # }); 49 | # EV::loop; 50 | 51 | runner 500000, $c, "ping",[]; 52 | -------------------------------------------------------------------------------- /temp/demo.pl: -------------------------------------------------------------------------------- 1 | use 5.010; 2 | use strict; 3 | use Test::More; 4 | use Test::Deep; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use EV; 8 | use EV::Tarantool16; 9 | use Time::HiRes 'sleep','time'; 10 | use Data::Dumper; 11 | use Errno; 12 | use Scalar::Util 'weaken'; 13 | # use Renewer; 14 | 15 | my $cfs = 0; 16 | my $connected; 17 | my $disconnected; 18 | 19 | my $tnt = { 20 | port => 3301, 21 | host => '127.0.0.1', 22 | username => 'test_user', 23 | password => 'test_pass', 24 | }; 25 | 26 | my $cnt = 0; 27 | my $max_cnt = 30000; 28 | 29 | my $c; $c = EV::Tarantool16->new({ 30 | host => $tnt->{host}, 31 | port => $tnt->{port}, 32 | # username => $tnt->{username}, 33 | # password => $tnt->{password}, 34 | 35 | # spaces => $realspaces, 36 | reconnect => 0.2, 37 | log_level => 5, 38 | connected => sub { 39 | warn "connected: @_"; 40 | $connected++; 41 | EV::unloop; 42 | }, 43 | connfail => sub { 44 | warn "connfail: @_"; 45 | EV::unloop; 46 | }, 47 | disconnected => sub { 48 | warn "discon: @_ / $!"; 49 | $disconnected++; 50 | EV::unloop; 51 | }, 52 | }); 53 | 54 | 55 | 56 | say "CONNECT 1"; 57 | $c->connect; 58 | EV::loop; 59 | 60 | # say "DISCONNECT"; 61 | # $c->disconnect; 62 | # EV::loop; 63 | 64 | # say "CONNECT 2"; 65 | # $c->connect; 66 | # EV::loop; 67 | 68 | # $c->update('tester', ['a1', 'a2', 12], [ [2 => '=', 18] ], sub { 69 | # say Dumper \@_; 70 | # EV::unloop; 71 | # }); 72 | # EV::loop; 73 | 74 | # $c->insert('tester', ['a1', 'a2', 12], sub { 75 | # say Dumper \@_; 76 | # EV::unloop; 77 | # }); 78 | # EV::loop; 79 | 80 | # $c->upsert('tester', {_t1 => 'a1', _t2 => 'a2', _t3 => 12, _t4 => 0}, [ [4 => '+', 1] ], sub { 81 | # say Dumper \@_; 82 | # EV::unloop; 83 | # }); 84 | # EV::loop; 85 | 86 | # my $data = { 87 | # id => 2, 88 | # d => { 89 | # a => 2 90 | # }, 91 | # }; 92 | 93 | # $c->insert('tester', $data, sub { 94 | # say Dumper \@_; 95 | # EV::unloop; 96 | # }); 97 | # EV::loop; 98 | 99 | $c->select('_vspace', [], sub { 100 | say Dumper \@_; 101 | EV::unloop; 102 | }); 103 | EV::loop; 104 | 105 | # $c->select('_vspace', ['_vspace'], {index => 'name'}, sub { 106 | # say Dumper \@_; 107 | # EV::unloop; 108 | # }); 109 | # EV::loop; 110 | 111 | # $c->insert('memier', [7, {a => 1, b => 2}], { in => 's*' }, sub { 112 | # say Dumper \@_; 113 | # EV::unloop; 114 | # }); 115 | # EV::loop; 116 | 117 | 118 | # $c->call('get_test_tuple', [], {space => 'tester'}, sub { 119 | # say Dumper \@_; 120 | # EV::unloop; 121 | # }); 122 | # EV::loop; 123 | 124 | # $c->update('test', [ 125 | # 'a', 126 | # 'b' 127 | # ], [ 128 | # [4 => '=', 'T'] 129 | # ], {index => 'ident'}, sub { 130 | # say Dumper \@_; 131 | # EV::unloop; 132 | # }); 133 | # EV::loop; 134 | 135 | # $c->ping("", sub { 136 | # say Dumper \@_; 137 | # EV::unloop; 138 | # }); 139 | # EV::loop; 140 | -------------------------------------------------------------------------------- /temp/init.lua: -------------------------------------------------------------------------------- 1 | box.cfg{ 2 | listen = 3301, 3 | background = true, 4 | log = 'tarantool.log', 5 | pid_file = 'tarantool.pid', 6 | } 7 | -------------------------------------------------------------------------------- /temp/leaks.pl: -------------------------------------------------------------------------------- 1 | use 5.010; 2 | use strict; 3 | use Test::More; 4 | use Test::Deep; 5 | use FindBin; 6 | use lib "t/lib","lib","$FindBin::Bin/../blib/lib","$FindBin::Bin/../blib/arch"; 7 | use EV; 8 | use EV::Tarantool16; 9 | use Time::HiRes 'sleep','time'; 10 | use Data::Dumper; 11 | use Errno; 12 | use Scalar::Util 'weaken'; 13 | use Renewer; 14 | use Devel::Leak; 15 | use Devel::Peek; 16 | use AE; 17 | 18 | my $var; 19 | # Devel::Leak::NoteSV($var); 20 | 21 | my $cfs = 0; 22 | my $connected; 23 | my $disconnected; 24 | 25 | my $tnt = { 26 | port => 3301, 27 | host => '127.0.0.1', 28 | username => 'test_user', 29 | password => 'test_pass', 30 | }; 31 | 32 | my $cnt = 0; 33 | my $max_cnt = 30000; 34 | 35 | 36 | Devel::Leak::NoteSV($var); 37 | 38 | my $c; $c = EV::Tarantool16->new({ 39 | host => $tnt->{host}, 40 | port => $tnt->{port}, 41 | username => $tnt->{username}, 42 | password => $tnt->{password}, 43 | reconnect => 0.2, 44 | log_level => 1, 45 | connected => sub { 46 | # diag Dumper \@_ unless $_[0]; 47 | $c->disconnect; 48 | return EV::unloop if ++$cnt >= $max_cnt; 49 | $c->connect; 50 | }, 51 | connfail => sub { 52 | my $c = shift; 53 | warn "@_ / $!"; 54 | EV::unloop; 55 | }, 56 | disconnected => sub { 57 | # warn "discon: @_ / $!"; 58 | # EV::unloop; 59 | }, 60 | }); 61 | 62 | $c->connect; 63 | EV::loop; 64 | undef $c; 65 | 66 | Devel::Leak::CheckSV($var); 67 | 68 | 69 | # $c->insert('memier', [7, {a => 1, b => 2}], { in => 's*' }, sub { 70 | # say Dumper \@_; 71 | # EV::unloop; 72 | # }); 73 | # EV::loop; 74 | 75 | 76 | # $c->call('get_test_tuple', [], {space => 'tester'}, sub { 77 | # say Dumper \@_; 78 | # EV::unloop; 79 | # }); 80 | # EV::loop; 81 | 82 | # $c->update('test', [ 83 | # 'a', 84 | # 'b' 85 | # ], [ 86 | # [4 => '=', 'T'] 87 | # ], {index => 'ident'}, sub { 88 | # say Dumper \@_; 89 | # EV::unloop; 90 | # }); 91 | # EV::loop; 92 | 93 | # $c->ping("", sub { 94 | # say Dumper \@_; 95 | # EV::unloop; 96 | # }); 97 | # EV::loop; 98 | 99 | # my $p = [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [4 => ':', 0, 3, 'romy'] ], { hash => 1 }]; 100 | 101 | # for (1..100000) { 102 | # $c->update('tester', $p->[0], $p->[1], $p->[2], sub { 103 | # my $a = @_[0]; 104 | # EV::unloop; 105 | # }); 106 | # EV::loop; 107 | # } 108 | # undef $p; 109 | # undef $c; 110 | 111 | # Devel::Leak::CheckSV($var); 112 | 113 | # $c->call("string_function1", [], {timeout => 10}, sub { 114 | # say Dumper \@_; 115 | # EV::unloop; 116 | # }); 117 | # EV::loop; 118 | 119 | # $c->select('_space', [], {hash => 0}, sub { 120 | # my ($a) = @_; 121 | # say Dumper \@_; 122 | # EV::unloop; 123 | # }); 124 | # EV::loop; 125 | 126 | 127 | # my $finished = 0; 128 | # $c->eval("return {box.info}", {}, sub { 129 | # my $a = @_[0]; 130 | # say 'hello'; 131 | # say Dumper \@_; 132 | # $finished = 1; 133 | # EV::unloop; 134 | # }); 135 | # if (!$finished) { 136 | # EV::loop; 137 | # } 138 | # undef $c; 139 | 140 | 141 | # } 142 | 143 | 144 | # for (1..10) { 145 | 146 | # $c->ping(sub { 147 | # # say Dumper \@_; 148 | # EV::unloop; 149 | # }); 150 | # EV::loop; 151 | 152 | # $c->select('tester', {_t1=>'t1', _t2=>'t2'}, {hash => 1, iterator => 'LE'}, sub { 153 | # my ($a) = @_; 154 | # # # my $size = @{$a->{tuples}->[0]}; 155 | # # # say $size; 156 | # say Dumper \@_; 157 | # EV::unloop; 158 | # }); 159 | # EV::loop; 160 | 161 | # $c->call("status_wait1", [], {timeout => 10}, sub { 162 | # say Dumper \@_; 163 | # EV::unloop; 164 | # }); 165 | # EV::loop; 166 | 167 | # $c->eval("return {box.space.tester:len{}}", [], sub { 168 | # say 'here'; 169 | # my $a = @_[0]; 170 | # say Dumper \@_; 171 | # EV::unloop; 172 | # }); 173 | # EV::loop; 174 | 175 | # $c->select('tester', {_t1=>'t1', _t2=>'t2'}, {hash => 1, iterator => 'LE'}, sub { 176 | # my ($a) = @_; 177 | # # # my $size = @{$a->{tuples}->[0]}; 178 | # # # say $size; 179 | # # say Dumper \@_; 180 | # EV::unloop; 181 | # }); 182 | # EV::loop; 183 | 184 | # } 185 | 186 | # undef $c; 187 | 188 | # Devel::Leak::CheckSV($var); 189 | 190 | 191 | # $c->select('tester', [], {hash=>0}, sub { 192 | # my ($a) = @_; 193 | # # my $size = @{$a->{tuples}->[0]}; 194 | # # say $size; 195 | # say Dumper \@_; 196 | # EV::unloop; 197 | # }); 198 | # EV::loop; 199 | 200 | 201 | # undef $c; 202 | # undef $tnt; 203 | # } 204 | 205 | 206 | # for (1..10) { 207 | # $c->ping(sub { 208 | # my $a = @_[0]; 209 | # say Dumper \@_ if !$a; 210 | # # is $a->{code}, 0; 211 | # EV::unloop; 212 | # }); 213 | # EV::loop; 214 | # } 215 | 216 | # my $p = [["t1", "t2", 101, '-100', { a => 11, b => 12, c => 13 }], { replace => 0, hash => 0 }]; 217 | 218 | # for (1..10) { 219 | # $c->insert('tester', $p->[0], $p->[1], sub { 220 | # my $a = @_[0]; 221 | 222 | # EV::unloop; 223 | # }); 224 | # EV::loop; 225 | # } 226 | # undef $p; 227 | 228 | # my $p = [{_t1=>'t1', _t2=>'t2'}, {hash => 1, iterator => 'LE'}]; 229 | # for (1..10) { 230 | # $c->select('tester', $p->[0], $p->[1], sub { 231 | # my $a = @_[0]; 232 | # EV::unloop; 233 | # }); 234 | # EV::loop; 235 | # } 236 | # undef $p; 237 | 238 | # my $p = [['tt1', 'tt2', 456], {}]; 239 | 240 | # for (1..10) { 241 | # $c->delete('tester', $p->[0], $p->[1], sub { 242 | # my $a = @_[0]; 243 | # EV::unloop; 244 | # }); 245 | # EV::loop; 246 | # } 247 | # undef $p; 248 | 249 | # my $p = [{_t1 => 't1',_t2 => 't2',_t3 => 17}, [ [4 => ':', 0, 3, 'romy'] ], { hash => 1 }]; 250 | 251 | # for (1..10) { 252 | # $c->update('tester', $p->[0], $p->[1], $p->[2], sub { 253 | # my $a = @_[0]; 254 | # EV::unloop; 255 | # }); 256 | # EV::loop; 257 | # } 258 | # undef $p; 259 | 260 | 261 | 262 | # Devel::Leak::CheckSV($var); 263 | -------------------------------------------------------------------------------- /xstarantool/encdec.h: -------------------------------------------------------------------------------- 1 | #ifndef _ENCDEC_H_ 2 | #define _ENCDEC_H_ 3 | 4 | #include "types.h" 5 | 6 | #define TNT_GREET_LENGTH 128 7 | #define TNT_VER_LENGTH 64 8 | #define TNT_SALT_LENGTH 44 9 | #define TNT_PAD_LENGTH ((TNT_GREET_LENGTH) - (TNT_VER_LENGTH) - (TNT_SALT_LENGTH)) 10 | 11 | static HV *types_boolean_stash; 12 | static SV *types_true, *types_false; 13 | 14 | #define PERL_UNDEF newSV(0) 15 | 16 | #define write_length(h, size) STMT_START { \ 17 | *h = 0xce; \ 18 | *((uint32_t *)(h+1)) = htobe32(size); \ 19 | } STMT_END 20 | 21 | #define write_iid(h, iid) STMT_START { \ 22 | *h = 0xce; \ 23 | *(uint32_t *)(h + 1) = htobe32(iid); \ 24 | h += 5; \ 25 | } STMT_END 26 | 27 | 28 | #define create_buffer(NAME, P_NAME, sz, tp_operation, iid) \ 29 | SV *NAME = sv_2mortal(newSV((sz))); \ 30 | SvUPGRADE(NAME, SVt_PV); \ 31 | SvPOK_on(NAME); \ 32 | \ 33 | char *P_NAME = (char *) SvPVX(NAME); \ 34 | P_NAME = mp_encode_map(P_NAME + 5, 2); \ 35 | P_NAME = mp_encode_uint(P_NAME, TP_CODE); \ 36 | P_NAME = mp_encode_uint(P_NAME, (tp_operation)); \ 37 | P_NAME = mp_encode_uint(P_NAME, TP_SYNC); \ 38 | write_iid(P_NAME, (iid)); \ 39 | 40 | #define sv_size_check(svx, svx_end, totalneed) STMT_START { \ 41 | if ( totalneed < SvLEN(svx) ) { \ 42 | } \ 43 | else { \ 44 | STRLEN used = svx_end - SvPVX(svx); \ 45 | svx_end = sv_grow(svx, totalneed); \ 46 | svx_end += used; \ 47 | } \ 48 | } STMT_END 49 | 50 | #define encode_keys(h, sz, fields, keys_size, fmt, key) STMT_START { \ 51 | uint32_t k; \ 52 | for (k = 0; k < keys_size; k++) { \ 53 | key = av_fetch( fields, k, 0 ); \ 54 | if (key && *key && SvOK(*key)) { \ 55 | char _fmt = k < fmt->size ? fmt->f[k] : fmt->def; \ 56 | h = encode_obj(*key, h, rv, &sz, _fmt); \ 57 | } else { \ 58 | h = encode_obj(&PL_sv_undef, h, rv, &sz, FMT_UNKNOWN); \ 59 | /*cwarn("Passed key is invalid. Consider revising.");*/ \ 60 | } \ 61 | } \ 62 | } STMT_END 63 | 64 | #define encode_str(dest, sz, rv, str, str_len) STMT_START { \ 65 | *sz += mp_sizeof_str(str_len); \ 66 | sv_size_check(rv, dest, *sz); \ 67 | dest = mp_encode_str(dest, str, str_len); \ 68 | } STMT_END 69 | 70 | #define encode_double(dest, sz, rv, v) STMT_START { \ 71 | *sz += mp_sizeof_double(v); \ 72 | sv_size_check(rv, dest, *sz); \ 73 | dest = mp_encode_double(dest, v); \ 74 | } STMT_END 75 | 76 | #define encode_uint(dest, sz, rv, v) STMT_START { \ 77 | *sz += mp_sizeof_uint(v); \ 78 | sv_size_check(rv, dest, *sz); \ 79 | dest = mp_encode_uint(dest, v); \ 80 | } STMT_END 81 | 82 | #define encode_int(dest, sz, rv, v) STMT_START { \ 83 | *sz += mp_sizeof_int(v); \ 84 | sv_size_check(rv, dest, *sz); \ 85 | dest = mp_encode_int(dest, v); \ 86 | } STMT_END 87 | 88 | #define encode_bool(dest, sz, rv, v) STMT_START { \ 89 | *sz += 1; \ 90 | sv_size_check(rv, dest, *sz); \ 91 | dest = mp_encode_bool(dest, v); \ 92 | } STMT_END 93 | 94 | #define encode_nil(dest, sz, rv) STMT_START { \ 95 | *sz += 1; \ 96 | sv_size_check(rv, dest, *sz); \ 97 | dest = mp_encode_nil(dest); \ 98 | } STMT_END 99 | 100 | #define encode_array(dest, sz, rv, arr_size) STMT_START { \ 101 | *sz += mp_sizeof_array(arr_size); \ 102 | sv_size_check(rv, dest, *sz); \ 103 | dest = mp_encode_array(dest, arr_size); \ 104 | } STMT_END 105 | 106 | #define encode_map(dest, sz, rv, keys_size) STMT_START { \ 107 | *sz += mp_sizeof_map(keys_size); \ 108 | sv_size_check(rv, dest, *sz); \ 109 | dest = mp_encode_map(dest, keys_size); \ 110 | } STMT_END 111 | 112 | #define REAL_SV(sv, real_sv, stash) \ 113 | HV *stash = NULL; \ 114 | SV *real_sv = NULL; \ 115 | if (SvROK(sv)) { \ 116 | real_sv = SvRV(sv); \ 117 | if (SvOBJECT(real_sv)) { \ 118 | stash = SvSTASH(real_sv); \ 119 | } \ 120 | } else { \ 121 | real_sv = sv; \ 122 | } 123 | 124 | 125 | #define encode_AV(src, rv, dest, sz) STMT_START {\ 126 | AV *arr = (AV *) src; \ 127 | uint32_t arr_size = av_len(arr) + 1; \ 128 | uint32_t i = 0; \ 129 | encode_array(dest, sz, rv, arr_size); \ 130 | \ 131 | SV **elem; \ 132 | for (i = 0; i < arr_size; ++i) { \ 133 | elem = av_fetch(arr, i, 0); \ 134 | if (elem && *elem && SvTYPE(*elem) != SVt_NULL) { \ 135 | dest = encode_obj(*elem, dest, rv, sz, FMT_UNKNOWN); \ 136 | } else { \ 137 | encode_nil(dest, sz, rv); \ 138 | } \ 139 | } \ 140 | } STMT_END 141 | 142 | 143 | #define encode_HV(src, rv, dest, sz) STMT_START { \ 144 | HV *hv = (HV *) src; \ 145 | HE *he; \ 146 | \ 147 | uint32_t keys_size = hv_iterinit(hv); \ 148 | \ 149 | encode_map(dest, sz, rv, keys_size); \ 150 | STRLEN nlen; \ 151 | while ((he = hv_iternext(hv))) { \ 152 | char *name = HePV(he, nlen); \ 153 | encode_str(dest, sz, rv, name, nlen); \ 154 | dest = encode_obj(HeVAL(he), dest, rv, sz, FMT_UNKNOWN); \ 155 | } \ 156 | } STMT_END 157 | 158 | 159 | 160 | static char *encode_obj(SV *initial_src, char *dest, SV *rv, size_t *sz, char fmt) { 161 | // cwarn("fmt = %d", fmt); 162 | 163 | SvGETMAGIC(initial_src); 164 | REAL_SV(initial_src, src, stash); 165 | 166 | if (fmt == FMT_STRING) { 167 | STRLEN str_len = 0; 168 | char *str = NULL; 169 | 170 | if (SvPOK(src)) { 171 | str = SvPV_nolen(src); 172 | str_len = SvCUR(src); 173 | } else { 174 | str = SvPV(src, str_len); 175 | str_len = SvCUR(src); 176 | } 177 | 178 | encode_str(dest, sz, rv, str, str_len); 179 | return dest; 180 | 181 | } else if (fmt == FMT_NUMBER || fmt == FMT_UNSIGNED || fmt == FMT_INTEGER) { 182 | 183 | if (fmt == FMT_NUMBER) { 184 | if (SvNOK(src)) { 185 | encode_double(dest, sz, rv, SvNVX(src)); 186 | return dest; 187 | } 188 | } 189 | 190 | if (SvIOK(src)) { 191 | if (SvUOK(src)) { 192 | encode_uint(dest, sz, rv, SvUVX(src)); 193 | return dest; 194 | } else { 195 | IV num = SvIVX(src); 196 | if (num >= 0) { 197 | encode_uint(dest, sz, rv, num); 198 | return dest; 199 | } else { 200 | encode_int(dest, sz, rv, num); 201 | return dest; 202 | } 203 | } 204 | } else if (SvPOK(src)) { 205 | if (fmt == FMT_NUMBER) { 206 | encode_double(dest, sz, rv, SvNV(src)); 207 | return dest; 208 | } else { 209 | NV num = SvNV(src); 210 | if (SvUOK(src)) { 211 | encode_uint(dest, sz, rv, SvUV(src)); 212 | return dest; 213 | } else { 214 | if (num >= 0) { 215 | encode_uint(dest, sz, rv, SvIV(src)); 216 | return dest; 217 | } else { 218 | encode_int(dest, sz, rv, SvIV(src)); 219 | return dest; 220 | } 221 | } 222 | } 223 | } else { 224 | croak("Incompatible types. Format expects: %c", fmt); 225 | } 226 | 227 | } else if (fmt == FMT_ARRAY) { 228 | 229 | if (SvTYPE(src) == SVt_PVAV) { 230 | encode_AV(src, rv, dest, sz); 231 | return dest; 232 | } else { 233 | croak("Incompatible types. Format expects: %c", fmt); 234 | } 235 | 236 | } else if (fmt == FMT_MAP) { 237 | 238 | if (SvTYPE(src) == SVt_PVHV) { 239 | encode_HV(src, rv, dest, sz); 240 | return dest; 241 | } else { 242 | croak("Incompatible types. Format expects: %c", fmt); 243 | } 244 | 245 | } else if (fmt == FMT_UNKNOWN || fmt == FMT_SCALAR || fmt == FMT_BOOLEAN) { 246 | 247 | HV *boolean_stash = types_boolean_stash ? types_boolean_stash : gv_stashpv ("Types::Serialiser::Boolean", 1); 248 | 249 | if (stash == boolean_stash) { 250 | bool v = (bool) SvIV(src); 251 | encode_bool(dest, sz, rv, v); 252 | return dest; 253 | } else { 254 | 255 | if (SvTYPE(src) == SVt_NULL) { 256 | encode_nil(dest, sz, rv); 257 | return dest; 258 | 259 | } else if (fmt != FMT_SCALAR && SvTYPE(src) == SVt_PVAV) { // array 260 | encode_AV(src, rv, dest, sz); 261 | return dest; 262 | 263 | } else if (fmt != FMT_SCALAR && SvTYPE(src) == SVt_PVHV) { // hash 264 | encode_HV(src, rv, dest, sz); 265 | return dest; 266 | 267 | } else if (SvNOK(src)) { // double 268 | encode_double(dest, sz, rv, SvNVX(src)); 269 | return dest; 270 | } else if (SvUOK(src)) { // uint 271 | encode_uint(dest, sz, rv, SvUVX(src)); 272 | return dest; 273 | 274 | } else if (SvIOK(src)) { // int or uint 275 | IV num = SvIVX(src); 276 | if (num >= 0) { 277 | encode_uint(dest, sz, rv, num); 278 | return dest; 279 | } else { 280 | encode_int(dest, sz, rv, num); 281 | return dest; 282 | } 283 | } else if (SvPOK(src)) { // string 284 | encode_str(dest, sz, rv, SvPV_nolen(src), SvCUR(src)); 285 | return dest; 286 | } else if (!SvOK(src)) { 287 | encode_nil(dest, sz, rv); 288 | } else { 289 | croak("What the heck are you trying to encode? (PV = %.*s) (type = %d)", SvCUR(src), SvPV_nolen(src), SvTYPE(src)); 290 | } 291 | } 292 | 293 | } else { 294 | croak("Not implemented"); 295 | } 296 | 297 | return dest; 298 | } 299 | 300 | 301 | #define decode_greeting(data, tnt_ver_begin, tnt_ver_end, salt_begin, salt_end) STMT_START {\ 302 | char *p = data; \ 303 | tnt_ver_begin = p; \ 304 | p += TNT_VER_LENGTH; \ 305 | tnt_ver_end = p; \ 306 | \ 307 | salt_begin = p; \ 308 | p += TNT_SALT_LENGTH; \ 309 | salt_end = p; \ 310 | \ 311 | p += TNT_PAD_LENGTH; \ 312 | } STMT_END 313 | 314 | #define decode_pkt_len_(h, out) STMT_START { \ 315 | char *p = *h; \ 316 | uint32_t l = *((uint32_t *)(p+1)); \ 317 | out = be32toh(l); \ 318 | } STMT_END 319 | 320 | static inline uint32_t decode_pkt_len(char **h) { 321 | char *p = *h; 322 | uint32_t l = *((uint32_t *)(p+1)); 323 | *h += 5; 324 | return be32toh(l); 325 | } 326 | 327 | 328 | static SV *decode_obj(const char **p) { 329 | uint32_t i = 0; 330 | const char *str = NULL; 331 | uint32_t str_len = 0; 332 | 333 | switch (mp_typeof(**p)) { 334 | case MP_UINT: { 335 | uint64_t value = mp_decode_uint(p); 336 | return (SV *) newSVuv(value); 337 | } 338 | case MP_INT: { 339 | int64_t value = mp_decode_int(p); 340 | return (SV *) newSViv(value); 341 | } 342 | case MP_STR: { 343 | str = mp_decode_str(p, &str_len); 344 | SV *sv = newSVpvn(str, str_len); 345 | sv_utf8_decode(sv); 346 | return sv; 347 | } 348 | case MP_BOOL: { 349 | bool value = mp_decode_bool(p); 350 | if (value) { 351 | return newSVsv(types_true); 352 | } else { 353 | return newSVsv(types_false); 354 | } 355 | } 356 | case MP_FLOAT: { 357 | float value = mp_decode_float(p); 358 | return (SV *) newSVnv((double) value); 359 | } 360 | case MP_DOUBLE: { 361 | double value = mp_decode_double(p); 362 | return (SV *) newSVnv(value); 363 | } 364 | case MP_ARRAY: { 365 | uint32_t arr_size = mp_decode_array(p); 366 | 367 | AV *arr = newAV(); 368 | av_extend(arr, arr_size); 369 | for (i = 0; i < arr_size; ++i) { 370 | av_push(arr, decode_obj(p)); 371 | } 372 | return newRV_noinc((SV *) arr); 373 | } 374 | 375 | case MP_MAP: { 376 | uint32_t map_size = mp_decode_map(p); 377 | // cwarn("map_size = %d", map_size); 378 | 379 | const char *map_key_str = NULL; 380 | uint32_t map_key_len = 0; 381 | SV *key; 382 | HV *hash = newHV(); 383 | for (i = 0; i < map_size; ++i) { 384 | switch(mp_typeof(**p)) { 385 | case MP_STR: { 386 | map_key_str = mp_decode_str(p, &map_key_len); 387 | key = newSVpvn(map_key_str, map_key_len); 388 | (void) sv_utf8_decode(key); 389 | break; 390 | } 391 | case MP_UINT: { 392 | uint64_t value = mp_decode_uint(p); 393 | key = newSVuv(value); 394 | break; 395 | } 396 | case MP_INT: { 397 | int64_t value = mp_decode_int(p); 398 | key = newSViv(value); 399 | break; 400 | } 401 | default: 402 | cwarn("Got unexpected type as a tuple map key"); 403 | mp_next(p); // skip the current key 404 | mp_next(p); // skip the value of current key 405 | continue; 406 | } 407 | SV *value = decode_obj(p); 408 | (void) hv_store_ent (hash, key, value, 0); 409 | SvREFCNT_dec(key); 410 | } 411 | return newRV_noinc((SV *) hash); 412 | } 413 | case MP_NIL: 414 | mp_next(p); 415 | return PERL_UNDEF; 416 | default: 417 | cwarn("Got unexpected type as a tuple element value"); 418 | mp_next(p); 419 | return PERL_UNDEF; 420 | } 421 | } 422 | 423 | 424 | #endif // _ENCDEC_H_ 425 | -------------------------------------------------------------------------------- /xstarantool/endian_compat.h: -------------------------------------------------------------------------------- 1 | #ifndef _ENDIAN_COMPAT_H_ 2 | #define _ENDIAN_COMPAT_H_ 3 | 4 | #ifndef le64toh 5 | 6 | #ifdef __APPLE__ 7 | # include 8 | # define bswap_16(x) OSSwapInt16(x) 9 | # define bswap_32(x) OSSwapInt32(x) 10 | # define bswap_64(x) OSSwapInt64(x) 11 | #elif __FreeBSD__ 12 | # include 13 | # define bswap_16(x) bswap16(x) 14 | # define bswap_32(x) bswap32(x) 15 | # define bswap_64(x) bswap64(x) 16 | #else 17 | # include 18 | # include 19 | #endif /* __APPLE__ */ 20 | 21 | 22 | # if __BYTE_ORDER == __LITTLE_ENDIAN 23 | 24 | #ifndef le16toh 25 | # define htobe16(x) bswap_16 (x) 26 | # define htole16(x) (x) 27 | # define be16toh(x) bswap_16 (x) 28 | # define le16toh(x) (x) 29 | #endif 30 | 31 | #ifndef le32toh 32 | # define htobe32(x) bswap_32 (x) 33 | # define htole32(x) (x) 34 | # define be32toh(x) bswap_32 (x) 35 | # define le32toh(x) (x) 36 | #endif 37 | 38 | #ifndef le64toh 39 | # define htobe64(x) bswap_64 (x) 40 | # define htole64(x) (x) 41 | # define be64toh(x) bswap_64 (x) 42 | # define le64toh(x) (x) 43 | #endif 44 | 45 | # else /* __BYTE_ORDER != __LITTLE_ENDIAN */ 46 | 47 | #ifndef le16toh 48 | # define htobe16(x) (x) 49 | # define htole16(x) bswap_16 (x) 50 | # define be16toh(x) (x) 51 | # define le16toh(x) bswap_16 (x) 52 | #endif 53 | 54 | #ifndef le32toh 55 | # define htobe32(x) (x) 56 | # define htole32(x) bswap_32 (x) 57 | # define be32toh(x) (x) 58 | # define le32toh(x) bswap_32 (x) 59 | #endif 60 | 61 | #ifndef le64toh 62 | # define htobe64(x) (x) 63 | # define htole64(x) bswap_64 (x) 64 | # define be64toh(x) (x) 65 | # define le64toh(x) bswap_64 (x) 66 | #endif 67 | 68 | #endif /* __BYTE_ORDER */ 69 | 70 | #endif /* le64toh */ 71 | 72 | #endif /* _ENDIAN_COMPAT_H_ */ 73 | -------------------------------------------------------------------------------- /xstarantool/log.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOG_H_ 2 | #define _LOG_H_ 3 | 4 | #define MAX_LOG_LEVEL 4 5 | #define _LOG_NONE 0 6 | #define _LOG_ERROR 1 7 | #define _LOG_WARN 2 8 | #define _LOG_INFO 3 9 | #define _LOG_DEBUG 4 10 | 11 | #ifndef _log 12 | #define _log(level, fmt, ...) do{ \ 13 | if (level == _LOG_NONE) break; \ 14 | switch (level) { \ 15 | case _LOG_ERROR: \ 16 | fprintf(stderr, "[ERROR] %s:%d: ", __FILE__, __LINE__); \ 17 | break; \ 18 | case _LOG_WARN: \ 19 | fprintf(stderr, "[WARN] %s:%d: ", __FILE__, __LINE__); \ 20 | break; \ 21 | case _LOG_INFO: \ 22 | fprintf(stderr, "[INFO] %s:%d: ", __FILE__, __LINE__); \ 23 | break; \ 24 | case _LOG_DEBUG: \ 25 | default: \ 26 | fprintf(stderr, "[DEBUG] %s:%d: ", __FILE__, __LINE__); \ 27 | break; \ 28 | } \ 29 | fprintf(stderr, fmt, ##__VA_ARGS__); \ 30 | if (fmt[strlen(fmt) - 1] != 0x0a) { fprintf(stderr, "\n"); } \ 31 | } while(0) 32 | 33 | #define _log_error(fmt, ...) _log(_LOG_ERROR, fmt, ##__VA_ARGS__) 34 | #define _log_warn(fmt, ...) _log(_LOG_WARN, fmt, ##__VA_ARGS__) 35 | #define _log_info(fmt, ...) _log(_LOG_INFO, fmt, ##__VA_ARGS__) 36 | #define _log_debug(fmt, ...) _log(_LOG_DEBUG, fmt, ##__VA_ARGS__) 37 | 38 | #define log(max_level, level, fmt, ...) do { \ 39 | if (level <= max_level) { \ 40 | _log(level, fmt, ##__VA_ARGS__); \ 41 | } \ 42 | } while(0) 43 | 44 | #define log_error(max_level, fmt, ...) log(max_level, _LOG_ERROR, fmt, ##__VA_ARGS__) 45 | #define log_warn(max_level, fmt, ...) log(max_level, _LOG_WARN, fmt, ##__VA_ARGS__) 46 | #define log_info(max_level, fmt, ...) log(max_level, _LOG_INFO, fmt, ##__VA_ARGS__) 47 | #define log_debug(max_level, fmt, ...) log(max_level, _LOG_DEBUG, fmt, ##__VA_ARGS__) 48 | #endif 49 | 50 | #endif // _LOG_H_ 51 | -------------------------------------------------------------------------------- /xstarantool/types.h: -------------------------------------------------------------------------------- 1 | #ifndef _TYPES_H_ 2 | #define _TYPES_H_ 3 | 4 | /* header */ 5 | enum tp_header_key_t { 6 | TP_CODE = 0x00, 7 | TP_SYNC = 0x01, 8 | TP_SERVER_ID = 0x02, 9 | TP_LSN = 0x03, 10 | TP_TIMESTAMP = 0x04, 11 | TP_SCHEMA_ID = 0x05, 12 | }; 13 | 14 | /* request body */ 15 | enum tp_body_key_t { 16 | TP_SPACE = 0x10, 17 | TP_INDEX = 0x11, 18 | TP_LIMIT = 0x12, 19 | TP_OFFSET = 0x13, 20 | TP_ITERATOR = 0x14, 21 | TP_KEY = 0x20, 22 | TP_TUPLE = 0x21, 23 | TP_FUNCTION = 0x22, 24 | TP_USERNAME = 0x23, 25 | TP_EXPRESSION = 0x27, 26 | TP_OPERATIONS = 0x28, 27 | }; 28 | 29 | /* response body */ 30 | enum tp_response_key_t { 31 | TP_DATA = 0x30, 32 | TP_ERROR = 0x31 33 | }; 34 | 35 | /* request types */ 36 | enum tp_request_type { 37 | TP_SELECT = 0x01, 38 | TP_INSERT = 0x02, 39 | TP_REPLACE = 0x03, 40 | TP_UPDATE = 0x04, 41 | TP_DELETE = 0x05, 42 | TP_CALL = 0x06, 43 | TP_AUTH = 0x07, 44 | TP_EVAL = 0x08, 45 | TP_UPSERT = 0x09, 46 | TP_PING = 0x40 47 | }; 48 | 49 | typedef struct { 50 | int code; 51 | int id; 52 | int schema_id; 53 | } tnt_header_t; 54 | 55 | void tnt_header_init(tnt_header_t *hdr) { 56 | hdr->code = -1; 57 | hdr->id = -1; 58 | hdr->schema_id = -1; 59 | } 60 | 61 | typedef struct { 62 | size_t size; 63 | char *f; 64 | int nofree; 65 | char def; 66 | } unpack_format; 67 | 68 | 69 | typedef struct { 70 | U32 id; 71 | SV *name; 72 | SV *type; 73 | HV *opts; 74 | AV *fields; 75 | unpack_format f; 76 | } TntIndex; 77 | 78 | typedef struct { 79 | U32 id; 80 | SV *name; 81 | SV *owner; 82 | SV *engine; 83 | SV *fields_count; 84 | SV *flags; 85 | 86 | AV *fields; 87 | HV *indexes; 88 | HV *field; 89 | 90 | unpack_format f; 91 | } TntSpace; 92 | 93 | typedef struct { 94 | ev_timer t; 95 | uint32_t id; 96 | void *self; 97 | SV *cb; 98 | SV *wbuf; 99 | U32 use_hash; 100 | uint8_t log_level; 101 | TntSpace *space; 102 | unpack_format *fmt; 103 | unpack_format f; 104 | char *call; 105 | } TntCtx; 106 | 107 | typedef struct { 108 | U32 id; 109 | char format; 110 | SV *name; 111 | } TntField; 112 | 113 | typedef enum { 114 | OP_UPD_ARITHMETIC, 115 | OP_UPD_DELETE, 116 | OP_UPD_INSERT_ASSIGN, 117 | OP_UPD_SPLICE, 118 | OP_UPD_UNKNOWN 119 | } update_op_type_t; 120 | 121 | typedef enum { 122 | FMT_BAD = -1, 123 | 124 | FMT_UNKNOWN = '*', 125 | FMT_UNSIGNED = 'u', 126 | FMT_STRING = 's', 127 | FMT_NUMBER = 'n', 128 | FMT_INTEGER = 'i', 129 | FMT_ARRAY = 'a', 130 | FMT_SCALAR = 'r', 131 | FMT_MAP = 'm', 132 | FMT_BOOLEAN = 'b' 133 | } tnt_format_t; 134 | 135 | typedef enum { 136 | TNT_IT_EQ = 0, 137 | TNT_IT_REQ = 1, 138 | TNT_IT_ALL = 2, 139 | TNT_IT_LT = 3, 140 | TNT_IT_LE = 4, 141 | TNT_IT_GE = 5, 142 | TNT_IT_GT = 6, 143 | TNT_IT_BITS_ALL_SET = 7, 144 | TNT_IT_BITS_ANY_SET = 8, 145 | TNT_IT_BITS_ALL_NOT_SET = 9, 146 | TNT_IT_OVERLAPS = 10, 147 | TNT_IT_NEIGHBOR = 11, 148 | TNT_IT_COUNT, 149 | } tnt_iterator_t; 150 | 151 | 152 | #endif // _TYPES_H_ 153 | -------------------------------------------------------------------------------- /xstarantool/xsmy.h: -------------------------------------------------------------------------------- 1 | #ifndef XSMY_H 2 | #define XSMY_H 3 | 4 | #include "endian_compat.h" 5 | #include 6 | 7 | #ifndef I64 8 | typedef int64_t I64; 9 | #endif 10 | 11 | #ifndef U64 12 | typedef uint64_t U64; 13 | #endif 14 | 15 | #ifdef HAS_QUAD 16 | #define HAS_LL 1 17 | #else 18 | #define HAS_LL 0 19 | #endif 20 | 21 | #ifndef cwarn 22 | #define cwarn(fmt, ...) do{ \ 23 | fprintf(stderr, "[WARN] %s:%d: ", __FILE__, __LINE__); \ 24 | fprintf(stderr, fmt, ##__VA_ARGS__); \ 25 | if (fmt[strlen(fmt) - 1] != 0x0a) { fprintf(stderr, "\n"); } \ 26 | } while(0) 27 | #endif 28 | 29 | #ifndef likely 30 | #define likely(x) __builtin_expect((x),1) 31 | #define unlikely(x) __builtin_expect((x),0) 32 | #endif 33 | 34 | #define dSVX(sv,ref,type) \ 35 | SV *sv = newSV( sizeof(type) );\ 36 | SvUPGRADE( sv, SVt_PV ); \ 37 | SvCUR_set(sv,sizeof(type)); \ 38 | SvPOKp_on(sv); \ 39 | type *ref = (type *) SvPVX( sv ); \ 40 | memset(ref,0,sizeof(type)); \ 41 | 42 | #ifndef dObjBy 43 | #define dObjBy(Type,obj,ptr,xx) Type *obj = (Type *) ( (char *) ptr - (ptrdiff_t) &((Type *) 0)-> xx ) 44 | #endif 45 | 46 | #define _croak_cb(cb,...) STMT_START { \ 47 | /* warn(__VA_ARGS__);*/ \ 48 | if (likely(cb != NULL)) { \ 49 | dSP; \ 50 | ENTER; \ 51 | SAVETMPS; \ 52 | PUSHMARK(SP); \ 53 | EXTEND(SP, 2); \ 54 | PUSHs(&PL_sv_undef); \ 55 | PUSHs( sv_2mortal(newSVpvf(__VA_ARGS__)) ); \ 56 | PUTBACK; \ 57 | call_sv( cb, G_DISCARD | G_VOID ); \ 58 | FREETMPS; \ 59 | LEAVE; \ 60 | } else { \ 61 | croak(__VA_ARGS__); \ 62 | } \ 63 | } STMT_END 64 | 65 | #define croak_cb(cb,...) STMT_START { \ 66 | _croak_cb(cb, __VA_ARGS__); \ 67 | return NULL; \ 68 | } STMT_END 69 | 70 | #define croak_cb_void(cb,...) STMT_START { \ 71 | _croak_cb(cb, __VA_ARGS__); \ 72 | return; \ 73 | } STMT_END 74 | 75 | #endif 76 | --------------------------------------------------------------------------------