├── .gitignore ├── project ├── build.properties └── plugins.sbt ├── src ├── test │ ├── resources │ │ └── test.conf │ └── scala │ │ └── com │ │ └── dongxiguo │ │ └── memcontinuationed │ │ ├── zero-log.config.scala │ │ ├── TestServerAddress.scala │ │ ├── RawAccessor.scala │ │ ├── UTF8Accessor.scala │ │ ├── SetTest.scala │ │ ├── AddOrAppendTest.scala │ │ ├── DeleteTest.scala │ │ ├── GetTest.scala │ │ ├── AppendTest.scala │ │ ├── PrependTest.scala │ │ ├── ReplaceTest.scala │ │ ├── RequireTest.scala │ │ ├── AddTest.scala │ │ ├── GetsTest.scala │ │ └── RequireForCasTest.scala └── main │ └── scala │ └── com │ └── dongxiguo │ └── memcontinuationed │ ├── CasId.scala │ ├── LogicException.scala │ ├── ExistsException.scala │ ├── NotStoredException.scala │ ├── ServerErrorException.scala │ ├── NotFoundException.scala │ ├── ProtocolException.scala │ ├── StorageAccessorComparator.scala │ ├── ProtobufAccessor.scala │ ├── StorageAccessor.scala │ ├── Exptime.scala │ ├── AsciiProtocol.scala │ └── Memcontinuationed.scala ├── .travis.yml ├── NOTICE ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.2 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.2") 2 | -------------------------------------------------------------------------------- /src/test/resources/test.conf: -------------------------------------------------------------------------------- 1 | memcontinuationed { 2 | memcached { 3 | host = "tt.btplug.dongxiguo.com" 4 | host = ${?TEST_MEMCACHED_HOST} 5 | port = 1978 6 | port = ${?TEST_MEMCACHED_PORT} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | scala: 4 | - 2.10.4 5 | - 2.11.2 6 | 7 | jdk: 8 | - oraclejdk7 9 | - openjdk7 10 | 11 | sudo: false 12 | 13 | services: 14 | - memcached 15 | 16 | env: 17 | - TEST_MEMCACHED_HOST=127.0.0.1 TEST_MEMCACHED_PORT=11211 18 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/CasId.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | private final class CasId(val value: Long) extends AnyVal 20 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/LogicException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | trait LogicException extends Exception 20 | 21 | // vim: set ts=2 sw=2 et: 22 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/ExistsException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | final class ExistsException extends Exception with LogicException 20 | 21 | // vim: set ts=2 sw=2 et: 22 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/NotStoredException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | final class NotStoredException extends Exception with LogicException 20 | 21 | // vim: set ts=2 sw=2 et: 22 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | memcontinuationed 2 | Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | 本作品(memcontinuationed)的著作权属于深圳市葡萄藤网络科技有限公司,首次发布于2013年。 17 | 18 | 我把本作品提供给你使用的前提是,你必须同意格式合同“Apache License 2.0”。“Apache License 2.0”是一种开源许可证,所以memcontinuationed是开源软件。合同中将赋予你使用、修改、发布等权利,但你需要接受一些义务。例如你必须在发布衍生作品时在某个地方(如“关于”、“制作名单”)显示本“NOTICE”文件,不得篡改作者,等等。详细的合同正文在“LISENCE”文本文件中。 19 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/ServerErrorException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | final class ServerErrorException(val msg: String) extends Exception(msg) with LogicException 20 | 21 | // vim: set ts=2 sw=2 et: 22 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/NotFoundException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | /** 20 | * 调用[[com.dongxiguo.memcontinuationed.Memcontinuationed]]的某些方法时找不到键 21 | */ 22 | final class NotFoundException extends Exception with LogicException 23 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/ProtocolException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | import java.io._ 20 | 21 | final class ProtocolException(msg: String = null, cause: Throwable = null) 22 | extends IOException(msg, cause) 23 | 24 | // vim: set ts=2 sw=2 et: 25 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/StorageAccessorComparator.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | import java.util.Comparator 20 | 21 | private[memcontinuationed] object StorageAccessorComparator 22 | extends Comparator[StorageAccessor[_]] { 23 | def compare(o1: StorageAccessor[_], o2: StorageAccessor[_]): Int = 24 | o1.key.compareTo(o2.key) 25 | } 26 | 27 | // vim: set ts=2 sw=2 et: 28 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/zero-log.config.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | import com.dongxiguo.zeroLog.Filter 20 | import com.dongxiguo.zeroLog.formatters.SimpleFormatter 21 | import com.dongxiguo.zeroLog.appenders.ConsoleAppender 22 | 23 | object ZeroLoggerFactory { 24 | final def newLogger(singleton: Singleton) = 25 | (Filter.Info, SimpleFormatter, ConsoleAppender) 26 | } 27 | 28 | // vim: set ts=2 sw=2 et: 29 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/TestServerAddress.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | import com.typesafe.config.ConfigFactory 21 | import java.net.InetSocketAddress 22 | 23 | object TestServerAddress { 24 | private[this] lazy val conf = ConfigFactory.load("test") 25 | 26 | /** 27 | * 根据键确定数据在哪个服务器上 28 | */ 29 | final def getAddress(accessor: StorageAccessor[_]) = 30 | new InetSocketAddress(conf.getString("memcontinuationed.memcached.host"), conf.getInt("memcontinuationed.memcached.port")) 31 | } 32 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/RawAccessor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | import java.io._ 20 | 21 | final class RawAccessor(override val key: String) 22 | extends StorageAccessor[Array[Byte]] { 23 | override final def encode(output: OutputStream, data: Array[Byte], flags: Int) { 24 | output.write(data) 25 | } 26 | 27 | override final def decode(input: InputStream, flags: Int): Array[Byte] = { 28 | val result = new Array[Byte](input.available) 29 | input.read(result) 30 | result 31 | } 32 | } 33 | 34 | // vim: set ts=2 sw=2 et: 35 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/UTF8Accessor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | import java.io._ 20 | 21 | final class UTF8Accessor(override val key: String) 22 | extends StorageAccessor[String] { 23 | override final def encode(output: OutputStream, data: String, flags: Int) { 24 | output.write(data.getBytes("UTF-8")) 25 | } 26 | 27 | override final def decode(input: InputStream, flags: Int): String = { 28 | val result = new Array[Byte](input.available) 29 | input.read(result) 30 | new String(result, "UTF-8") 31 | } 32 | } 33 | 34 | 35 | // vim: set ts=2 sw=2 et: 36 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/ProtobufAccessor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | import java.io._ 19 | import com.google.protobuf._ 20 | 21 | /** A key in [[https://code.google.com/p/protobuf/ Protocol Buffers]] format. 22 | * 23 | * You should implement your own `ProtobufAccessor` for each type of value. 24 | * 25 | * @tparam Value the decoded value. 26 | */ 27 | trait ProtobufAccessor[Value <: Message] extends StorageAccessor[Value] { 28 | 29 | /** The default instance of the value */ 30 | def defaultInstance: Value 31 | 32 | def encode(output: OutputStream, data: Value, flags: Int) { 33 | data.writeTo(output) 34 | } 35 | 36 | def decode(input: InputStream, flags: Int): Value = { 37 | defaultInstance.toBuilder.mergeFrom(input).build.asInstanceOf[Value] 38 | } 39 | } 40 | 41 | // vim: set ts=2 sw=2 et: 42 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/StorageAccessor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | import java.io._ 19 | 20 | /** A memcached key. 21 | * 22 | * You should implement your own `StorageAccessor` for each type of value. 23 | * 24 | * @tparam Value the decoded value. 25 | */ 26 | trait StorageAccessor[Value] { 27 | 28 | /** The underlying key sent to server */ 29 | val key: String 30 | 31 | /** Returns the flags for `data`, which will be sent to server. 32 | * 33 | * @param data the value 34 | */ 35 | def getFlags(data: Value): Int = 0 36 | 37 | /** Returns the decoded value. 38 | * 39 | * @param input the byte stream to produce the value 40 | * @param flags the flags for the value 41 | */ 42 | def decode(input: InputStream, flags: Int): Value 43 | 44 | /** Encodes the value. 45 | * 46 | * @param output the byte stream the value writes to 47 | * @param value the value to be encoded 48 | * @param flags the flags for the value 49 | */ 50 | def encode(output: OutputStream, value: Value, flags: Int) 51 | } 52 | 53 | // vim: set ts=2 sw=2 et: 54 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/Exptime.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | import java.util.Date 20 | 21 | /** The expiration time sent to server 22 | * 23 | * @param underlying the underlying number sent to server 24 | */ 25 | final class Exptime private(val underlying: Long) extends AnyVal 26 | 27 | /** Factory to create `Exptime` instance */ 28 | object Exptime { 29 | 30 | /** The expiration time that never be reached. */ 31 | final val NeverExpires: Exptime = new Exptime(0L) 32 | 33 | /** Returns an expiration time at `date` 34 | * 35 | * @param date the expiration date 36 | */ 37 | final def unixTime(date: Date): Exptime = { 38 | val result = (date.getTime / 1000).toInt 39 | if (result >= 0 && result <= 60 * 60 * 24 * 30 || result > 0xFFFFFFFFL) { 40 | throw new IllegalArgumentException("The date must be larger than 30 days") 41 | } 42 | new Exptime(result) 43 | } 44 | 45 | /** Returns an expiration time at some number of seconds from now on 46 | * 47 | * @param seconds the number of seconds from now on 48 | */ 49 | final def fromNowOn(seconds: Int): Exptime = { 50 | if (seconds >= 0 && seconds <= 60 * 60 * 24 * 30) { 51 | new Exptime(seconds) 52 | } else { 53 | throw new IllegalArgumentException("The date must be shorter than 30 days") 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/SetTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | 21 | import org.junit._ 22 | import java.util.concurrent._ 23 | import java.nio.channels._ 24 | import java.io._ 25 | import scala.util.continuations._ 26 | import scala.util.control.Exception.Catcher 27 | 28 | object SetTest { 29 | implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 30 | } 31 | 32 | class SetTest { 33 | import SetTest._ 34 | 35 | @Test 36 | def stored() { 37 | synchronized { 38 | var isFailed = false; 39 | val executor = Executors.newFixedThreadPool(1) 40 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 41 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 42 | val accessor = new UTF8Accessor("memcontinuationed_set_test_stored") 43 | 44 | implicit def defaultCatcher: Catcher[Unit] = { 45 | case e: Exception => 46 | synchronized { 47 | isFailed = true 48 | notify() 49 | } 50 | } 51 | reset { 52 | memcontinuationed.set(accessor, "1") 53 | val result = memcontinuationed.require(accessor) 54 | synchronized { 55 | result match { 56 | case "1" => 57 | case _ => 58 | isFailed = true 59 | } 60 | notify() 61 | } 62 | } 63 | wait() 64 | reset { 65 | memcontinuationed.delete(accessor) 66 | synchronized { 67 | notify() 68 | } 69 | } 70 | wait() 71 | memcontinuationed.shutdown() 72 | channelGroup.shutdown() 73 | if (isFailed) { 74 | Assert.fail() 75 | } 76 | } 77 | } 78 | } 79 | 80 | // vim: set ts=2 sw=2 et: 81 | 82 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/AddOrAppendTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | 21 | import org.junit._ 22 | import java.util.concurrent._ 23 | import java.nio.channels._ 24 | import java.io._ 25 | import scala.util.continuations._ 26 | import scala.util.control.Exception.Catcher 27 | 28 | object AddOrAppendTest { 29 | implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 30 | } 31 | 32 | class AddOrAppendTest { 33 | import AddOrAppendTest._ 34 | 35 | @Test 36 | def stored() { 37 | synchronized { 38 | var isFailed = false 39 | val executor = Executors.newFixedThreadPool(1) 40 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 41 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 42 | val accessor = new UTF8Accessor("memcontinuationed_addOrAppend_test_stored") 43 | 44 | implicit def defaultCatcher: Catcher[Unit] = { 45 | case e: Exception => 46 | synchronized { 47 | isFailed = true 48 | notify() 49 | } 50 | } 51 | reset { 52 | memcontinuationed.addOrAppend(accessor, "1") 53 | if (memcontinuationed.require(accessor) == "1") { 54 | memcontinuationed.addOrAppend(accessor, "2") 55 | val result = memcontinuationed.require(accessor) 56 | synchronized { 57 | result match { 58 | case "12" => 59 | case _ => 60 | isFailed = true 61 | } 62 | } 63 | } else { 64 | synchronized { 65 | isFailed = true 66 | } 67 | shiftUnit[Unit, Unit, Unit]() 68 | } 69 | synchronized { 70 | notify() 71 | } 72 | } 73 | wait() 74 | reset { 75 | memcontinuationed.delete(accessor) 76 | synchronized { 77 | notify() 78 | } 79 | } 80 | wait() 81 | memcontinuationed.shutdown() 82 | channelGroup.shutdown() 83 | if (isFailed) { 84 | Assert.fail() 85 | } 86 | } 87 | } 88 | } 89 | 90 | // vim: set ts=2 sw=2 et: 91 | 92 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/DeleteTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | 21 | import org.junit._ 22 | import java.util.concurrent._ 23 | import java.nio.channels._ 24 | import java.io._ 25 | import scala.util.continuations._ 26 | import scala.util.control.Exception.Catcher 27 | import com.dongxiguo.commons.continuations.CollectionConverters._ 28 | 29 | object DeleteTest { 30 | implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 31 | } 32 | 33 | class DeleteTest { 34 | import DeleteTest._ 35 | 36 | @Test 37 | def deleted() { 38 | synchronized { 39 | var isFailed = false; 40 | val executor = Executors.newFixedThreadPool(1) 41 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 42 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 43 | val accessor = new UTF8Accessor("memcontinuationed_delete_test_deleted") 44 | 45 | implicit def defaultCatcher: Catcher[Unit] = { 46 | case e: Exception => 47 | synchronized { 48 | isFailed = true 49 | notify() 50 | } 51 | } 52 | reset { 53 | memcontinuationed.add(accessor, "1") 54 | memcontinuationed.delete(accessor) 55 | synchronized { 56 | notify() 57 | } 58 | } 59 | wait() 60 | memcontinuationed.shutdown() 61 | channelGroup.shutdown() 62 | if (isFailed) { 63 | Assert.fail() 64 | } 65 | } 66 | } 67 | 68 | @Test 69 | def notFound() { 70 | synchronized { 71 | var isFailed = false; 72 | val executor = Executors.newFixedThreadPool(1) 73 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 74 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 75 | val accessor = new UTF8Accessor("memcontinuationed_delete_test_notFound") 76 | 77 | implicit def defaultCatcher: Catcher[Unit] = { 78 | case e: Exception => 79 | synchronized { 80 | isFailed = true 81 | notify() 82 | } 83 | } 84 | reset { 85 | memcontinuationed.delete(accessor)({ 86 | case e: NotFoundException => 87 | synchronized { 88 | notify() 89 | } 90 | case e: Exception => 91 | synchronized { 92 | isFailed = true 93 | notify() 94 | } 95 | }) 96 | synchronized { 97 | isFailed = true 98 | notify() 99 | } 100 | } 101 | wait() 102 | memcontinuationed.shutdown() 103 | channelGroup.shutdown() 104 | if (isFailed) { 105 | Assert.fail() 106 | } 107 | } 108 | } 109 | } 110 | 111 | // vim: set ts=2 sw=2 et: 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/GetTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | 21 | import org.junit._ 22 | import java.util.concurrent._ 23 | import java.nio.channels._ 24 | import java.io._ 25 | import scala.util.continuations._ 26 | import scala.util.control.Exception.Catcher 27 | import com.dongxiguo.commons.continuations.CollectionConverters._ 28 | 29 | object GetTest { 30 | implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 31 | } 32 | 33 | class GetTest { 34 | import GetTest._ 35 | 36 | @Test 37 | def got() { 38 | synchronized { 39 | var isFailed = false; 40 | val executor = Executors.newFixedThreadPool(1) 41 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 42 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 43 | val accessor1 = new UTF8Accessor("memcontinuationed_get_test_got_1") 44 | val accessor2 = new UTF8Accessor("memcontinuationed_get_test_got_2") 45 | val accessor3 = new UTF8Accessor("memcontinuationed_get_test_got_3") 46 | 47 | implicit def defaultCatcher: Catcher[Unit] = { 48 | case e: Exception => 49 | synchronized { 50 | isFailed = true 51 | notify() 52 | } 53 | } 54 | reset { 55 | memcontinuationed.add(accessor1, "1") 56 | memcontinuationed.add(accessor3, "3") 57 | val results = memcontinuationed.get(accessor1, accessor2, accessor3) 58 | results.get(accessor1) match { 59 | case None => 60 | synchronized { 61 | isFailed = true 62 | } 63 | case Some(result) if result != "1" => 64 | synchronized { 65 | isFailed = true 66 | } 67 | case _ => 68 | } 69 | results.get(accessor2) match { 70 | case Some(result) => 71 | synchronized { 72 | isFailed = true 73 | } 74 | case _ => 75 | } 76 | results.get(accessor3) match { 77 | case None => 78 | synchronized { 79 | isFailed = true 80 | } 81 | case Some(result) if result != "3" => 82 | synchronized { 83 | isFailed = true 84 | } 85 | case _ => 86 | } 87 | synchronized { 88 | notify() 89 | } 90 | } 91 | wait() 92 | reset { 93 | memcontinuationed.delete(accessor1) 94 | memcontinuationed.delete(accessor3) 95 | synchronized { 96 | notify() 97 | } 98 | } 99 | wait() 100 | memcontinuationed.shutdown() 101 | channelGroup.shutdown() 102 | if (isFailed) { 103 | Assert.fail() 104 | } 105 | } 106 | } 107 | } 108 | // vim: set ts=2 sw=2 et: 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/AppendTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | 21 | import org.junit._ 22 | import java.util.concurrent._ 23 | import java.nio.channels._ 24 | import java.io._ 25 | import scala.util.continuations._ 26 | import scala.util.control.Exception.Catcher 27 | 28 | object AppendTest { 29 | implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 30 | } 31 | 32 | class AppendTest { 33 | import AppendTest._ 34 | 35 | @Test 36 | def stored() { 37 | synchronized { 38 | var isFailed = false; 39 | val executor = Executors.newFixedThreadPool(1) 40 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 41 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 42 | val accessor = new UTF8Accessor("memcontinuationed_append_test_stored") 43 | 44 | implicit def defaultCatcher: Catcher[Unit] = { 45 | case e: Exception => 46 | synchronized { 47 | isFailed = true 48 | notify() 49 | } 50 | } 51 | 52 | reset { 53 | memcontinuationed.add(accessor, "1") 54 | memcontinuationed.append(accessor, "2") 55 | val result = memcontinuationed.require(accessor) 56 | synchronized { 57 | result match { 58 | case "12" => 59 | case _ => 60 | isFailed = true 61 | } 62 | notify() 63 | } 64 | } 65 | wait() 66 | reset { 67 | memcontinuationed.delete(accessor) 68 | synchronized { 69 | notify() 70 | } 71 | } 72 | wait() 73 | memcontinuationed.shutdown() 74 | channelGroup.shutdown() 75 | if (isFailed) { 76 | Assert.fail() 77 | } 78 | } 79 | } 80 | 81 | @Test 82 | def notStored() { 83 | synchronized { 84 | var isFailed = false; 85 | val executor = Executors.newFixedThreadPool(1) 86 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 87 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 88 | val accessor = new UTF8Accessor("memcontinuationed_append_test_not_stored") 89 | 90 | implicit def defaultCatcher: Catcher[Unit] = { 91 | case e: Exception => 92 | synchronized { 93 | isFailed = true 94 | notify() 95 | } 96 | } 97 | 98 | reset { 99 | memcontinuationed.append(accessor, "2") { 100 | case e: NotStoredException => 101 | synchronized { 102 | notify() 103 | } 104 | case e: Exception => 105 | synchronized { 106 | isFailed = true 107 | notify() 108 | } 109 | } 110 | synchronized { 111 | isFailed = true 112 | notify() 113 | } 114 | } 115 | wait() 116 | memcontinuationed.shutdown() 117 | channelGroup.shutdown() 118 | if (isFailed) { 119 | Assert.fail() 120 | } 121 | } 122 | } 123 | } 124 | 125 | // vim: set ts=2 sw=2 et: 126 | 127 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/PrependTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | 21 | import org.junit._ 22 | import java.util.concurrent._ 23 | import java.nio.channels._ 24 | import java.io._ 25 | import scala.util.continuations._ 26 | import scala.util.control.Exception.Catcher 27 | 28 | object PrependTest { 29 | implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 30 | } 31 | 32 | class PrependTest { 33 | import PrependTest._ 34 | 35 | @Test 36 | def stored() { 37 | synchronized { 38 | var isFailed = false; 39 | val executor = Executors.newFixedThreadPool(1) 40 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 41 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 42 | val accessor = new UTF8Accessor("memcontinuationed_prepend_test_stored") 43 | 44 | implicit def defaultCatcher: Catcher[Unit] = { 45 | case e: Exception => 46 | synchronized { 47 | isFailed = true 48 | notify() 49 | } 50 | } 51 | reset { 52 | memcontinuationed.add(accessor, "1") 53 | memcontinuationed.prepend(accessor, "0") 54 | val result = memcontinuationed.require(accessor) 55 | synchronized { 56 | result match { 57 | case "01" => 58 | case _ => 59 | isFailed = true 60 | } 61 | notify() 62 | } 63 | } 64 | wait() 65 | reset { 66 | memcontinuationed.delete(accessor) 67 | synchronized { 68 | notify() 69 | } 70 | } 71 | wait() 72 | memcontinuationed.shutdown() 73 | channelGroup.shutdown() 74 | if (isFailed) { 75 | Assert.fail() 76 | } 77 | } 78 | } 79 | 80 | @Test 81 | def notStored() { 82 | synchronized { 83 | var isFailed = false; 84 | val executor = Executors.newFixedThreadPool(1) 85 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 86 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 87 | val accessor = new UTF8Accessor("memcontinuationed_prepend_test_not_stored") 88 | 89 | implicit def defaultCatcher: Catcher[Unit] = { 90 | case e: Exception => 91 | synchronized { 92 | isFailed = true 93 | notify() 94 | } 95 | } 96 | reset { 97 | memcontinuationed.prepend(accessor, "0"){ 98 | case e: NotStoredException => 99 | synchronized { 100 | notify() 101 | } 102 | case e: Exception => 103 | synchronized { 104 | isFailed = true 105 | notify() 106 | } 107 | } 108 | synchronized { 109 | isFailed = true 110 | notify() 111 | } 112 | } 113 | wait() 114 | memcontinuationed.shutdown() 115 | channelGroup.shutdown() 116 | if (isFailed) { 117 | Assert.fail() 118 | } 119 | } 120 | } 121 | } 122 | 123 | // vim: set ts=2 sw=2 et: 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/ReplaceTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | 21 | import org.junit._ 22 | import java.util.concurrent._ 23 | import java.nio.channels._ 24 | import java.io._ 25 | import scala.util.continuations._ 26 | import scala.util.control.Exception.Catcher 27 | 28 | object ReplaceTest { 29 | implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 30 | } 31 | 32 | class ReplaceTest { 33 | import ReplaceTest._ 34 | 35 | @Test 36 | def stored() { 37 | synchronized { 38 | var isFailed = false; 39 | val executor = Executors.newFixedThreadPool(1) 40 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 41 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 42 | val accessor = new UTF8Accessor("memcontinuationed_replace_test_stored") 43 | 44 | implicit def defaultCatcher: Catcher[Unit] = { 45 | case e: Exception => 46 | synchronized { 47 | isFailed = true 48 | notify() 49 | } 50 | } 51 | reset { 52 | memcontinuationed.add(accessor, "1") 53 | memcontinuationed.replace(accessor, "2") 54 | val result = memcontinuationed.require(accessor) 55 | synchronized { 56 | result match { 57 | case "2" => 58 | case _ => 59 | isFailed = true 60 | } 61 | notify() 62 | } 63 | } 64 | wait() 65 | reset { 66 | memcontinuationed.delete(accessor) 67 | synchronized { 68 | notify() 69 | } 70 | } 71 | wait() 72 | memcontinuationed.shutdown() 73 | channelGroup.shutdown() 74 | if (isFailed) { 75 | Assert.fail() 76 | } 77 | } 78 | } 79 | 80 | @Test 81 | def notStored() { 82 | synchronized { 83 | var isFailed = false; 84 | val executor = Executors.newFixedThreadPool(1) 85 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 86 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 87 | val accessor = new UTF8Accessor("memcontinuationed_replace_test_not_stored") 88 | 89 | implicit def defaultCatcher: Catcher[Unit] = { 90 | case e: Exception => 91 | synchronized { 92 | isFailed = true 93 | notify() 94 | } 95 | } 96 | reset { 97 | memcontinuationed.replace(accessor, "2") { 98 | case e: NotStoredException => 99 | synchronized { 100 | notify() 101 | } 102 | case e: Exception => 103 | synchronized { 104 | isFailed = true 105 | notify() 106 | } 107 | } 108 | synchronized { 109 | isFailed = true 110 | notify() 111 | } 112 | } 113 | wait() 114 | memcontinuationed.shutdown() 115 | channelGroup.shutdown() 116 | if (isFailed) { 117 | Assert.fail() 118 | } 119 | } 120 | } 121 | } 122 | 123 | // vim: set ts=2 sw=2 et: 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/RequireTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | 21 | import org.junit._ 22 | import java.util.concurrent._ 23 | import java.nio.channels._ 24 | import java.io._ 25 | import scala.util.continuations._ 26 | import scala.util.control.Exception.Catcher 27 | import com.dongxiguo.commons.continuations.CollectionConverters._ 28 | 29 | object RequireTest { 30 | implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 31 | } 32 | 33 | class RequireTest { 34 | import RequireTest._ 35 | 36 | @Test 37 | def got() { 38 | synchronized { 39 | var isFailed = false; 40 | val executor = Executors.newFixedThreadPool(1) 41 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 42 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 43 | val accessor = new UTF8Accessor("memcontinuationed_require_test_got") 44 | 45 | implicit def defaultCatcher: Catcher[Unit] = { 46 | case e: Exception => 47 | synchronized { 48 | isFailed = true 49 | notify() 50 | } 51 | } 52 | reset { 53 | memcontinuationed.add(accessor, "1") 54 | val result = memcontinuationed.require(accessor) 55 | synchronized { 56 | result match { 57 | case "1" => 58 | case _ => 59 | isFailed = true 60 | } 61 | notify() 62 | } 63 | } 64 | wait() 65 | reset { 66 | memcontinuationed.delete(accessor) 67 | synchronized { 68 | notify() 69 | } 70 | } 71 | wait() 72 | memcontinuationed.shutdown() 73 | channelGroup.shutdown() 74 | if (isFailed) { 75 | Assert.fail() 76 | } 77 | } 78 | } 79 | 80 | @Test 81 | def notFound() { 82 | synchronized { 83 | var isFailed = false; 84 | val executor = Executors.newFixedThreadPool(1) 85 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 86 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 87 | val accessor = new UTF8Accessor("memcontinuationed_require_test_notFound") 88 | 89 | implicit def defaultCatcher: Catcher[Unit] = { 90 | case e: Exception => 91 | synchronized { 92 | isFailed = true 93 | notify() 94 | } 95 | } 96 | reset { 97 | memcontinuationed.require(accessor)({ 98 | case e: NotFoundException => 99 | synchronized { 100 | notify() 101 | } 102 | case e: Exception => 103 | synchronized { 104 | isFailed = true 105 | notify() 106 | } 107 | }, manifest[String]) 108 | synchronized { 109 | isFailed = true 110 | notify() 111 | } 112 | } 113 | wait() 114 | memcontinuationed.shutdown() 115 | channelGroup.shutdown() 116 | if (isFailed) { 117 | Assert.fail() 118 | } 119 | } 120 | } 121 | } 122 | 123 | // vim: set ts=2 sw=2 et: 124 | 125 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/AddTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | 21 | import org.junit._ 22 | import java.util.concurrent._ 23 | import java.nio.channels._ 24 | import java.io._ 25 | import scala.util.continuations._ 26 | import scala.util.control.Exception.Catcher 27 | 28 | object AddTest { 29 | implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 30 | } 31 | 32 | class AddTest { 33 | import AddTest._ 34 | 35 | @Test 36 | def stored() { 37 | synchronized { 38 | var isFailed = false; 39 | val executor = Executors.newFixedThreadPool(1) 40 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 41 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 42 | val accessor = new UTF8Accessor("memcontinuationed_add_test_stored") 43 | 44 | implicit def defaultCatcher: Catcher[Unit] = { 45 | case e: Exception => 46 | synchronized { 47 | isFailed = true 48 | notify() 49 | } 50 | } 51 | 52 | reset { 53 | memcontinuationed.add(accessor, "1") 54 | val result = memcontinuationed.require(accessor) 55 | synchronized { 56 | result match { 57 | case "1" => 58 | case _ => 59 | isFailed = true 60 | } 61 | notify() 62 | } 63 | } 64 | wait() 65 | 66 | reset { 67 | memcontinuationed.delete(accessor) 68 | synchronized { 69 | notify() 70 | } 71 | } 72 | wait() 73 | memcontinuationed.shutdown() 74 | channelGroup.shutdown() 75 | if (isFailed) { 76 | Assert.fail() 77 | } 78 | } 79 | } 80 | 81 | @Test 82 | def notStored() { 83 | synchronized { 84 | var isFailed = false; 85 | val executor = Executors.newFixedThreadPool(1) 86 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 87 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 88 | val accessor = new UTF8Accessor("memcontinuationed_add_test_not_stored") 89 | 90 | implicit def defaultCatcher: Catcher[Unit] = { 91 | case e: Exception => 92 | synchronized { 93 | isFailed = true 94 | notify() 95 | } 96 | } 97 | reset { 98 | memcontinuationed.add(accessor, "1") 99 | memcontinuationed.add(accessor, "2") { 100 | case e: NotStoredException => 101 | synchronized { 102 | notify() 103 | } 104 | case e: Exception => 105 | synchronized { 106 | isFailed = true 107 | notify() 108 | } 109 | } 110 | synchronized { 111 | isFailed = true 112 | notify() 113 | } 114 | } 115 | wait() 116 | 117 | reset { 118 | memcontinuationed.delete(accessor) 119 | synchronized { 120 | notify() 121 | } 122 | } 123 | wait() 124 | memcontinuationed.shutdown() 125 | channelGroup.shutdown() 126 | if (isFailed) { 127 | Assert.fail() 128 | } 129 | } 130 | } 131 | } 132 | 133 | // vim: set ts=2 sw=2 et: 134 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/GetsTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | 21 | import org.junit._ 22 | import java.util.concurrent._ 23 | import java.nio.channels._ 24 | import java.io._ 25 | import scala.util.continuations._ 26 | import scala.util.control.Exception.Catcher 27 | import com.dongxiguo.commons.continuations.CollectionConverters._ 28 | 29 | object GetsTest { 30 | implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 31 | } 32 | 33 | class GetsTest { 34 | import GetTest._ 35 | 36 | @Test 37 | def cas() { 38 | synchronized { 39 | var isFailed = false; 40 | val executor = Executors.newFixedThreadPool(1) 41 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 42 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 43 | val accessor1 = new UTF8Accessor("memcontinuationed_gets_test_got_1") 44 | val accessor2 = new UTF8Accessor("memcontinuationed_gets_test_got_2") 45 | val accessor3 = new UTF8Accessor("memcontinuationed_gets_test_got_3") 46 | 47 | implicit def defaultCatcher: Catcher[Unit] = { 48 | case e: Exception => 49 | synchronized { 50 | isFailed = true 51 | notify() 52 | } 53 | } 54 | reset { 55 | memcontinuationed.add(accessor1, "1") 56 | memcontinuationed.add(accessor3, "3") 57 | val mutables = memcontinuationed.gets(accessor1, accessor2, accessor3) 58 | 59 | { 60 | val _ = mutables.get(accessor1) match { 61 | case None => 62 | synchronized { 63 | isFailed = true 64 | } 65 | shiftUnit0[Unit, Unit]() 66 | case Some(mutable) => 67 | mutable.casUntilSuccess { origin => 68 | origin + "2" 69 | } 70 | val result = memcontinuationed.require(accessor1) 71 | if (result != "12") { 72 | synchronized { 73 | isFailed = true 74 | } 75 | } 76 | } 77 | mutables.get(accessor2) match { 78 | case Some(result) => 79 | synchronized { 80 | isFailed = true 81 | } 82 | case _ => 83 | } 84 | } 85 | val _ = mutables.get(accessor3) match { 86 | case None => 87 | synchronized { 88 | isFailed = true 89 | } 90 | shiftUnit0[Unit, Unit]() 91 | case Some(mutable) => 92 | mutable.casUntilSuccess { origin => 93 | origin + "2" 94 | } 95 | val result = memcontinuationed.require(accessor3) 96 | if (result != "32") { 97 | synchronized { 98 | isFailed = true 99 | } 100 | } 101 | } 102 | synchronized { 103 | notify() 104 | } 105 | } 106 | wait() 107 | reset { 108 | memcontinuationed.delete(accessor1) 109 | memcontinuationed.delete(accessor3) 110 | synchronized { 111 | notify() 112 | } 113 | } 114 | wait() 115 | memcontinuationed.shutdown() 116 | channelGroup.shutdown() 117 | if (isFailed) { 118 | Assert.fail() 119 | } 120 | } 121 | } 122 | } 123 | // vim: set ts=2 sw=2 et: 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Memcontinuationed 2 | ================= 3 | 4 |
Build Status
5 | 6 | **Memcontinuationed** is an asynchronous memcached client for [Scala](http://www.scala-lang.org/). 7 | Memcontinuationed is the fastest memcached client on JVM, 8 | much faster than [spymemcached](https://code.google.com/p/spymemcached/) or 9 | [Whalin's client](https://github.com/gwhalin/Memcached-Java-Client/wiki). 10 | 11 | ## Why is Memcontinuationed so fast? 12 | 13 | ### Reason 1. Better threading model 14 | 15 | Memcontinuationed never blocks any threads. On the other hand, spymemcached does not block the IO thread but it does 16 | block the user's thread. All Memcontinuationed API are 17 | [`@suspendable`](http://www.scala-lang.org/api/current/scala/util/continuations/package.html#suspendable=scala.util.continuations.package.cps%5BUnit%5D), 18 | which mean these methods can be invoked by a thread, and return to another thread. 19 | 20 | ### Reason 2. Optimization for huge number of IOPS 21 | 22 | Memcontinuationed can merge multiply `get` or `gets` requests into one request. 23 | On the other hand, spymemcached sends all requests immediately, never waiting for previous response. 24 | The spymemcached way consumes more CPU and more TCP overheads than Memcontinuationed. 25 | Even worse, the spymemcached way is not compatible with [some memcached server](http://wiki.open.qq.com/wiki/%E8%AE%BF%E9%97%AECMEM). 26 | 27 | Note: Because Memcontinuationed queues requests until all the previous response have been received, 28 | you may need to create a connection pool of `com.dongxiguo.memcontinuationed.Memcontinuationed` to maximize the IOPS. 29 | 30 | ## A sample to use Memcontinuationed 31 | 32 | ```scala 33 | import com.dongxiguo.memcontinuationed.Memcontinuationed 34 | import com.dongxiguo.memcontinuationed.StorageAccessor 35 | import java.io._ 36 | import java.net._ 37 | import java.nio.channels.AsynchronousChannelGroup 38 | import java.util.concurrent.Executors 39 | import scala.util.continuations.reset 40 | import scala.util.control.Exception.Catcher 41 | 42 | object Sample { 43 | 44 | def main(args: Array[String]) { 45 | val threadPool = Executors.newCachedThreadPool() 46 | val channelGroup = AsynchronousChannelGroup.withThreadPool(threadPool) 47 | 48 | // The locator determines where the memcached server is. 49 | // You may want to implement ketama hashing here. 50 | def locator(accessor: StorageAccessor[_]) = { 51 | new InetSocketAddress("localhost", 1978) 52 | } 53 | 54 | val memcontinuationed = new Memcontinuationed(channelGroup, locator) 55 | 56 | // The error handler 57 | implicit def catcher:Catcher[Unit] = { 58 | case e: Exception => 59 | scala.Console.err.print(e) 60 | sys.exit(-1) 61 | } 62 | 63 | reset { 64 | memcontinuationed.set(MyKey("hello"), "Hello, World!") 65 | val result = memcontinuationed.require(MyKey("hello")) 66 | assert(result == "Hello, World!") 67 | println(result) 68 | sys.exit() 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * `MyKey` specifies how to serialize the data of a key/value pair. 75 | */ 76 | case class MyKey(override val key: String) extends StorageAccessor[String] { 77 | 78 | override def encode(output: OutputStream, data: String, flags: Int) { 79 | output.write(data.getBytes("UTF-8")) 80 | } 81 | 82 | override def decode(input: InputStream, flags: Int): String = { 83 | val result = new Array[Byte](input.available) 84 | input.read(result) 85 | new String(result, "UTF-8") 86 | } 87 | } 88 | ``` 89 | 90 | There is something you need to know: 91 | 92 | * `get`, `set`, and most of other methods in `Memcontinuationed` are `@suspendable`. You must invoke them in `reset` or in another `@suspendable` function you defined. 93 | * `get`, `set`, and most of other methods in `Memcontinuationed` accept an implicit parameter `Catcher`. You must use `Catcher` to handle exceptions from `@suspendable` functions, instead of `try`/`catch`. 94 | * `MyKey` is the key you passed to server, which is a custom `StorageAccessor`. You should implement your own `StorageAccessor` for each type of data. If your value's format is [Protocol Buffers](http://code.google.com/p/protobuf/), you can use `com.dongxiguo.memcontinuationed.ProtobufAccessor` as your custom key's super class. 95 | 96 | ## Build configuration 97 | 98 | Add these lines to your `build.sbt` if you use [Sbt](http://www.scala-sbt.org/): 99 | 100 | ```scala 101 | libraryDependencies += "com.dongxiguo" %% "memcontinuationed" % "0.3.2" 102 | 103 | libraryDependencies <++= scalaBinaryVersion { bv => 104 | bv match { 105 | case "2.10" => { 106 | Seq() 107 | } 108 | case _ => { 109 | Seq("org.scala-lang.plugins" % s"scala-continuations-library_$bv" % "1.0.1") 110 | } 111 | } 112 | } 113 | 114 | libraryDependencies <+= scalaVersion { sv => 115 | if (sv.startsWith("2.10.")) { 116 | compilerPlugin("org.scala-lang.plugins" % "continuations" % sv) 117 | } else { 118 | compilerPlugin("org.scala-lang.plugins" % s"scala-continuations-plugin_$sv" % "1.0.1") 119 | } 120 | } 121 | 122 | scalacOptions += "-P:continuations:enable" 123 | ``` 124 | 125 | ### Requirement 126 | 127 | Memcontinuationed requires Scala 2.10.x or 2.11.x, and JRE 7. 128 | 129 | ## Links 130 | 131 | * [The API documentation](http://central.maven.org/maven2/com/dongxiguo/memcontinuationed_2.11/0.3.2/memcontinuationed_2.11-0.3.2-javadoc.jar) 132 | * [Coding examples](https://github.com/Atry/memcontinuationed/tree/master/src/test/scala/com/dongxiguo/memcontinuationed) 133 | -------------------------------------------------------------------------------- /src/test/scala/com/dongxiguo/memcontinuationed/RequireForCasTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com 18 | package dongxiguo 19 | package memcontinuationed 20 | 21 | import org.junit._ 22 | import java.util.concurrent._ 23 | import java.nio.channels._ 24 | import java.io._ 25 | import scala.util.continuations._ 26 | import scala.util.control.Exception.Catcher 27 | import com.dongxiguo.commons.continuations.CollectionConverters._ 28 | 29 | object RequireForCasTest { 30 | implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 31 | } 32 | 33 | class RequireForCasTest { 34 | import RequireForCasTest._ 35 | 36 | @Test 37 | def cas() { 38 | synchronized { 39 | var isFailed = false; 40 | val executor = Executors.newFixedThreadPool(1) 41 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 42 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 43 | val accessor = new UTF8Accessor("memcontinuationed_requireForCas_test_cas") 44 | 45 | implicit def defaultCatcher: Catcher[Unit] = { 46 | case e: Exception => 47 | synchronized { 48 | isFailed = true 49 | notify() 50 | } 51 | } 52 | reset { 53 | memcontinuationed.add(accessor, "1") 54 | val mutable = memcontinuationed.requireForCAS(accessor) 55 | mutable.casUntilSuccess{ origin => 56 | origin + "2" 57 | } 58 | val result = memcontinuationed.require(accessor) 59 | synchronized { 60 | result match { 61 | case "12" => 62 | case _ => 63 | isFailed = true 64 | } 65 | notify() 66 | } 67 | } 68 | wait() 69 | reset { 70 | memcontinuationed.delete(accessor) 71 | synchronized { 72 | notify() 73 | } 74 | } 75 | wait() 76 | memcontinuationed.shutdown() 77 | channelGroup.shutdown() 78 | if (isFailed) { 79 | Assert.fail() 80 | } 81 | } 82 | } 83 | 84 | @Test 85 | def continuationedCas() { 86 | synchronized { 87 | var isFailed = false; 88 | val executor = Executors.newFixedThreadPool(1) 89 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 90 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 91 | val accessor = new UTF8Accessor("memcontinuationed_requireForCas_test_continuationedCas") 92 | 93 | implicit def defaultCatcher: Catcher[Unit] = { 94 | case e: Exception => 95 | synchronized { 96 | isFailed = true 97 | notify() 98 | } 99 | } 100 | reset { 101 | memcontinuationed.add(accessor, "1") 102 | val mutable = memcontinuationed.requireForCAS(accessor) 103 | mutable.continuationizedCASUntilSuccess { origin => 104 | shift { (countinue: String => Unit) => 105 | synchronized { 106 | notify() 107 | } 108 | } 109 | synchronized { 110 | isFailed = true 111 | notify() 112 | } 113 | origin 114 | } 115 | synchronized { 116 | isFailed = true 117 | notify() 118 | } 119 | } 120 | wait() 121 | reset { 122 | memcontinuationed.delete(accessor) 123 | synchronized { 124 | notify() 125 | } 126 | } 127 | wait() 128 | memcontinuationed.shutdown() 129 | channelGroup.shutdown() 130 | if (isFailed) { 131 | Assert.fail() 132 | } 133 | } 134 | } 135 | 136 | 137 | @Test 138 | def notFound() { 139 | synchronized { 140 | var isFailed = false; 141 | val executor = Executors.newFixedThreadPool(1) 142 | val channelGroup = AsynchronousChannelGroup.withThreadPool(executor) 143 | val memcontinuationed = new Memcontinuationed(channelGroup, TestServerAddress.getAddress _) 144 | val accessor = new UTF8Accessor("memcontinuationed_requireForCas_test_notFound") 145 | 146 | implicit def defaultCatcher: Catcher[Unit] = { 147 | case e: Exception => 148 | synchronized { 149 | isFailed = true 150 | notify() 151 | } 152 | } 153 | reset { 154 | memcontinuationed.requireForCAS(accessor)({ 155 | case e: NotFoundException => 156 | synchronized { 157 | notify() 158 | } 159 | case e: Exception => 160 | synchronized { 161 | isFailed = true 162 | notify() 163 | } 164 | }, manifest[String]) 165 | synchronized { 166 | isFailed = true 167 | notify() 168 | } 169 | } 170 | wait() 171 | memcontinuationed.shutdown() 172 | channelGroup.shutdown() 173 | if (isFailed) { 174 | Assert.fail() 175 | } 176 | } 177 | } 178 | } 179 | 180 | // vim: set ts=2 sw=2 et: 181 | 182 | 183 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 1999-2005 The Apache Software Foundation 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/AsciiProtocol.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | import java.util.Collections 20 | import java.util.Arrays 21 | import java.util.concurrent._ 22 | import java.util.concurrent.atomic._ 23 | import java.nio.channels._ 24 | import java.nio._ 25 | import java.io._ 26 | import java.net._ 27 | import scala.util.control.Exception.Catcher 28 | import scala.util.continuations._ 29 | import scala.collection.JavaConverters._ 30 | import com.dongxiguo.commons.continuations.io._ 31 | import com.dongxiguo.commons.continuations._ 32 | import com.google.protobuf._ 33 | import com.dongxiguo.zeroLog.context.CurrentMethodName 34 | import com.dongxiguo.zeroLog.context.CurrentSource 35 | import com.dongxiguo.zeroLog.context.CurrentLine 36 | import com.dongxiguo.zeroLog.context.CurrentClass 37 | import com.dongxiguo.fastring.Fastring.Implicits._ 38 | import com.dongxiguo.fastring.Fastring 39 | 40 | private[memcontinuationed] object AsciiProtocol { 41 | 42 | implicit private val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 43 | 44 | private def raise(throwable: Throwable)( 45 | implicit catcher: Catcher[Unit], 46 | currentSource: CurrentSource, 47 | currentLine: CurrentLine, 48 | currentClassName: CurrentClass, 49 | currentMethodNameOption: Option[CurrentMethodName]): Nothing @suspendable = { 50 | logger.severe(throwable) 51 | if (catcher.isDefinedAt(throwable)) { 52 | catcher(throwable) 53 | } else { 54 | throw throwable 55 | } 56 | shift(Hang) 57 | } 58 | 59 | private final val MaxValueSize = 32768 60 | 61 | implicit private class LineReader(val inputStream: AsynchronousInputStream) { 62 | 63 | @throws(classOf[IOException]) 64 | private[AsciiProtocol] final def readUntilCRLF( 65 | packetLimit: Int = MaxValueSize, 66 | out: ByteArrayOutputStream = new ByteArrayOutputStream)( 67 | implicit catcher: Catcher[Unit]): String @suspendable = { 68 | var rest = packetLimit 69 | var cr, lf: Int = 0 70 | while (lf != -1 && rest != 0 && (cr != '\r' || lf != '\n')) { 71 | inputStream.available = 1 72 | cr = lf 73 | lf = inputStream.read() 74 | out.write(lf) 75 | rest = rest - 1 76 | } 77 | if (cr == '\r' && lf == '\n') { 78 | out.toString("UTF-8") 79 | } else { 80 | if (rest == 0) { 81 | raise(new ProtocolException("Line is too long.")) 82 | } else { 83 | assert(lf == -1) 84 | raise( 85 | new ProtocolException("Unexpected end of data from socket.")) 86 | } 87 | } 88 | } 89 | } 90 | 91 | private val KeyRegex = """[^\p{Cntrl}\s]+""".r 92 | 93 | private val GetsLineRegex = 94 | """\s*VALUE\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\r\n""".r 95 | 96 | /** 97 | * @return `_2`和`_3`中每一项都与`_1`中每一项一一对应。不存在的键值对在`_2`中为`null` 98 | */ 99 | @throws(classOf[IOException]) 100 | @throws(classOf[ProtocolException]) 101 | final def gets[Value]( 102 | socket: AsynchronousSocketChannel, 103 | accessors: StorageAccessor[Value]*)( 104 | implicit catcher: Catcher[Unit], 105 | m1: Manifest[Value], 106 | m2: Manifest[StorageAccessor[Value]]): (Array[StorageAccessor[Value]], Array[Value], Array[CasId]) @suspendable = { 107 | logger.fine { 108 | fast"Request ${accessors.mkFastring(",")} on $socket" 109 | } 110 | def loggedCatcher: Catcher[Unit] = { 111 | case e: IOException => 112 | logger.severe(e) 113 | SuspendableException.catchOrThrow(e) 114 | } 115 | val outputStream = new ByteArrayOutputStream 116 | val writer = new OutputStreamWriter(outputStream, "UTF-8") 117 | writer.write("gets") 118 | for (accessor <- accessors) { 119 | val key @ KeyRegex() = accessor.key 120 | writer.write(' ') 121 | writer.write(key) 122 | } 123 | writer.write("\r\n") 124 | writer.flush() 125 | logger.finer { 126 | fast"Request buffer is full filled:\n${new String(outputStream.toByteArray, "UTF-8")}" 127 | } 128 | sendAndReceive[(Array[StorageAccessor[Value]], Array[Value], Array[CasId])]( 129 | socket, Array(ByteBuffer.wrap(outputStream.toByteArray))) { inputStream => 130 | val sortedAccessors = accessors.toArray 131 | Arrays.sort[StorageAccessor[Value]](sortedAccessors, StorageAccessorComparator) 132 | val values = Array.ofDim[Value](accessors.length) 133 | val casIds = Array.ofDim[CasId](accessors.length) 134 | 135 | logger.finer("Waiting for response...") 136 | // Workaround for https://issues.scala-lang.org/browse/SI-6579 137 | @inline def parseLine(line: String): Unit @suspendable = { 138 | line match { 139 | case GetsLineRegex(key, flags, numBytes, casId) if numBytes.toInt <= MaxValueSize => 140 | logger.fine { 141 | fast"Receive a header for $key(${ 142 | if (flags == 0) 143 | fast"" 144 | else 145 | fast"flags=$flags," 146 | }numBytes=$numBytes,casId=$casId)" 147 | } 148 | (inputStream.available = numBytes.toInt)(loggedCatcher) 149 | import language.postfixOps 150 | val index = 151 | Collections.binarySearch( 152 | sortedAccessors.view map { _.key } asJava, 153 | key) 154 | if (index < 0) { 155 | raise( 156 | new ProtocolException("Receive an unexpected key: " + key)) 157 | } else { 158 | val accessor = sortedAccessors(index) 159 | values(index) = accessor.decode(inputStream, flags.toInt) 160 | if (inputStream.available > 0) { 161 | logger.info { 162 | fast"Decoder for $accessor does not consume all data." 163 | } 164 | inputStream.skip(inputStream.available) 165 | } 166 | casIds(index) = new CasId(casId.toLong) 167 | (inputStream.available = 2)(loggedCatcher) 168 | if (inputStream.read() != '\r') { 169 | raise( 170 | new ProtocolException( 171 | "There must be CRLF after binary data.")) 172 | } else { 173 | if (inputStream.read() != '\n') { 174 | raise( 175 | new ProtocolException( 176 | "There must be CRLF after binary data.")) 177 | } 178 | } 179 | } 180 | case ServerErrorRegex(reason) => 181 | raise(new ServerErrorException(reason)) 182 | case _ => 183 | raise(new ProtocolException("Bad line " + line)) 184 | } 185 | } 186 | 187 | // Workaround for https://issues.scala-lang.org/browse/SI-6603 188 | var line: String = null 189 | line = inputStream.readUntilCRLF() 190 | while (line != "END\r\n") { 191 | parseLine(line) 192 | line = inputStream.readUntilCRLF() 193 | } 194 | logger.finer("Receive \"END\"") 195 | (sortedAccessors, values, casIds) 196 | }(loggedCatcher) 197 | } 198 | 199 | private def sendAndReceive[A]( 200 | s: AsynchronousSocketChannel, 201 | request: Array[ByteBuffer])( 202 | responseHandler: AsynchronousInputStream => A @suspendable)( 203 | implicit catcher: Catcher[Unit]): A @suspendable = { 204 | shift( 205 | new AtomicInteger(2) with ((A => Unit) => Unit) { 206 | var a: Any = null 207 | override final def apply(continue: A => Unit) { 208 | reset { 209 | logger.finest("Send request...") 210 | writeAll(s, request, 1L, TimeUnit.SECONDS) 211 | logger.finest("Request is sent.") 212 | if (decrementAndGet() == 0) { 213 | continue(a.asInstanceOf[A]) 214 | } 215 | } 216 | reset { 217 | a = responseHandler( 218 | new AsynchronousInputStream { 219 | override val socket = s 220 | override def readingTimeout = 3L 221 | override def readingTimeoutUnit = TimeUnit.SECONDS 222 | }) 223 | if (decrementAndGet() == 0) { 224 | continue(a.asInstanceOf[A]) 225 | } 226 | } 227 | } 228 | }) 229 | } 230 | 231 | private val GetLineRegex = 232 | """\s*VALUE\s+(\S+)\s+(\d+)\s+(\d+)(?:\s+\d*)?\r\n""".r 233 | 234 | /** 235 | * @return `_2`中每一项都与`_1`中每一项一一对应。不存在的键值对在`_2`中为`null` 236 | */ 237 | final def get[Value]( 238 | socket: AsynchronousSocketChannel, 239 | accessors: StorageAccessor[Value]*)( 240 | implicit catcher: Catcher[Unit], 241 | m1: Manifest[Value], 242 | m2: Manifest[StorageAccessor[Value]]): (Array[StorageAccessor[Value]], Array[Value]) @suspendable = { 243 | logger.fine { 244 | fast"Request ${accessors.mkFastring(",")} on $socket" 245 | } 246 | def loggedCatcher: Catcher[Unit] = { 247 | case e: IOException => 248 | logger.severe(e) 249 | SuspendableException.catchOrThrow(e) 250 | } 251 | val outputStream = new ByteArrayOutputStream 252 | val writer = new OutputStreamWriter(outputStream, "UTF-8") 253 | writer.write("get") 254 | for (accessor <- accessors) { 255 | val key @ KeyRegex() = accessor.key 256 | writer.write(' ') 257 | writer.write(key) 258 | } 259 | writer.write("\r\n") 260 | writer.flush() 261 | logger.finer("Request buffer is full filled.") 262 | logger.finest(new String(outputStream.toByteArray, "UTF-8")) 263 | sendAndReceive[(Array[StorageAccessor[Value]], Array[Value])]( 264 | socket, Array(ByteBuffer.wrap(outputStream.toByteArray))) { inputStream => 265 | logger.finest("GET reading...") 266 | val sortedAccessors = accessors.toArray 267 | Arrays.sort[StorageAccessor[Value]]( 268 | sortedAccessors, 269 | StorageAccessorComparator) 270 | val values = Array.ofDim[Value](accessors.length) 271 | 272 | logger.finer("Waiting for response...") 273 | // Workaround for https://issues.scala-lang.org/browse/SI-6579 274 | @inline def parseLine(line: String): Unit @suspendable = { 275 | logger.finest { fast"GET response line: $line" } 276 | line match { 277 | case GetLineRegex(key, flags, numBytes) if numBytes.toInt <= MaxValueSize => 278 | logger.fine { 279 | fast"Receive a header for $key(${ 280 | if (flags == 0) 281 | fast"" 282 | else 283 | fast"flags=$flags," 284 | }numBytes=$numBytes)" 285 | } 286 | (inputStream.available = numBytes.toInt)(loggedCatcher) 287 | import language.postfixOps 288 | val index = 289 | Collections.binarySearch( 290 | sortedAccessors.view map { _.key } asJava, 291 | key) 292 | if (index < 0) { 293 | raise( 294 | new ProtocolException( 295 | "Response key " + key + " have not been requested.")) 296 | } else { 297 | val accessor = sortedAccessors(index) 298 | values(index) = accessor.decode(inputStream, flags.toInt) 299 | if (inputStream.available > 0) { 300 | logger.info { 301 | fast"Decoder for $accessor does not consume all data." 302 | } 303 | inputStream.skip(inputStream.available) 304 | } 305 | 306 | (inputStream.available = 2)(loggedCatcher) 307 | if (inputStream.read() != '\r') { 308 | raise( 309 | new ProtocolException( 310 | "There must be CRLF after binary data.")) 311 | } else { 312 | if (inputStream.read() != '\n') { 313 | raise( 314 | new ProtocolException( 315 | "There must be CRLF after binary data.")) 316 | } 317 | } 318 | } 319 | case ServerErrorRegex(reason) => 320 | raise(new ServerErrorException(reason)) 321 | case _ => 322 | raise(new ProtocolException("Bad line " + line)) 323 | } 324 | } 325 | 326 | // Workaround for https://issues.scala-lang.org/browse/SI-6603 327 | var line: String = null 328 | line = inputStream.readUntilCRLF() 329 | while (line != "END\r\n") { 330 | parseLine(line) 331 | line = inputStream.readUntilCRLF() 332 | } 333 | logger.finer("Receive \"END\"") 334 | (sortedAccessors, values) 335 | }(loggedCatcher) 336 | } 337 | 338 | private val ServerErrorRegex = """SERVER_ERROR\s+(.+)\s*\r\n""".r 339 | 340 | final def cas[Value]( 341 | socket: AsynchronousSocketChannel, 342 | accessor: StorageAccessor[Value], 343 | value: Value, 344 | casId: CasId, 345 | exptime: Exptime)( 346 | implicit catcher: Catcher[Unit]): Unit @suspendable = { 347 | val headOutputStream = new ByteArrayOutputStream 348 | val headWriter = new OutputStreamWriter(headOutputStream, "UTF-8") 349 | headWriter.write("cas ") 350 | val key @ KeyRegex() = accessor.key 351 | headWriter.write(key) 352 | headWriter.write(' ') 353 | val flags = accessor.getFlags(value) 354 | headWriter.write(flags.toString) 355 | headWriter.write(' ') 356 | headWriter.write(exptime.underlying.toString) 357 | headWriter.write(' ') 358 | val dataOutputStream = new ByteArrayOutputStream 359 | accessor.encode(dataOutputStream, value, flags) 360 | val data = dataOutputStream.toByteArray 361 | headWriter.write(dataOutputStream.size.toString) 362 | headWriter.write(' ') 363 | headWriter.write(casId.value.toString) 364 | headWriter.write("\r\n") 365 | headWriter.flush() 366 | dataOutputStream.write('\r') 367 | dataOutputStream.write('\n') 368 | dataOutputStream.flush() 369 | logger.fine("CAS send head: " + headOutputStream.toString("UTF-8")) 370 | logger.fine("CAS send data: " + dataOutputStream.toString("UTF-8")) 371 | sendAndReceive[Unit](socket, Array( 372 | ByteBuffer.wrap(headOutputStream.toByteArray), 373 | ByteBuffer.wrap(dataOutputStream.toByteArray))) { inputStream => 374 | val line = inputStream.readUntilCRLF() 375 | line match { 376 | case ServerErrorRegex(reason) => 377 | raise(new ServerErrorException(reason)) 378 | case "NOT_STORED\r\n" => 379 | raise(new NotStoredException) 380 | case "EXISTS\r\n" => 381 | raise(new ExistsException) 382 | case "NOT_FOUND\r\n" => 383 | raise(new NotFoundException) 384 | case "STORED\r\n" => 385 | shiftUnit0[Unit, Unit]() 386 | case _ => 387 | raise(new ProtocolException("Bad line " + line)) 388 | } 389 | 390 | } 391 | } 392 | 393 | @throws(classOf[ExistsException]) 394 | @throws(classOf[NotStoredException]) 395 | @throws(classOf[NotFoundException]) 396 | @throws(classOf[ServerErrorException]) 397 | @throws(classOf[ProtocolException]) 398 | final def store[Value]( 399 | socket: AsynchronousSocketChannel, 400 | command: String, 401 | accessor: StorageAccessor[Value], 402 | value: Value, 403 | exptime: Exptime)( 404 | implicit catcher: Catcher[Unit]): Unit @suspendable = { 405 | val headOutputStream = new ByteArrayOutputStream 406 | val headWriter = new OutputStreamWriter(headOutputStream, "UTF-8") 407 | headWriter.write(command) 408 | headWriter.write(' ') 409 | val key @ KeyRegex() = accessor.key 410 | headWriter.write(key) 411 | headWriter.write(' ') 412 | val flags = accessor.getFlags(value) 413 | headWriter.write(flags.toString) 414 | headWriter.write(' ') 415 | headWriter.write(exptime.underlying.toString) 416 | headWriter.write(' ') 417 | val dataOutputStream = new ByteArrayOutputStream 418 | accessor.encode(dataOutputStream, value, flags) 419 | val data = dataOutputStream.toByteArray 420 | headWriter.write(dataOutputStream.size.toString) 421 | headWriter.write("\r\n") 422 | headWriter.flush() 423 | dataOutputStream.write('\r') 424 | dataOutputStream.write('\n') 425 | dataOutputStream.flush() 426 | logger.fine("STORE send head: " + headOutputStream.toString("UTF-8")) 427 | logger.fine("STORE send data: " + dataOutputStream.toString("UTF-8")) 428 | sendAndReceive[Unit]( 429 | socket, 430 | Array(ByteBuffer.wrap(headOutputStream.toByteArray), 431 | ByteBuffer.wrap(dataOutputStream.toByteArray))) { inputStream => 432 | val line = inputStream.readUntilCRLF() 433 | line match { 434 | case ServerErrorRegex(reason) => 435 | raise(new ServerErrorException(reason)) 436 | case "NOT_STORED\r\n" => 437 | raise(new NotStoredException) 438 | case "EXISTS\r\n" => 439 | raise(new ExistsException) 440 | case "NOT_FOUND\r\n" => 441 | raise(new NotFoundException) 442 | case "STORED\r\n" => 443 | shiftUnit0[Unit, Unit]() 444 | case _ => 445 | raise(new ProtocolException("Bad line " + line)) 446 | } 447 | 448 | } 449 | } 450 | 451 | @throws(classOf[NotFoundException]) 452 | @throws(classOf[ServerErrorException]) 453 | @throws(classOf[ProtocolException]) 454 | final def delete[Value]( 455 | socket: AsynchronousSocketChannel, 456 | accessor: StorageAccessor[Value])( 457 | implicit catcher: Catcher[Unit]): Unit @suspendable = { 458 | val headOutputStream = new ByteArrayOutputStream 459 | val headWriter = new OutputStreamWriter(headOutputStream, "UTF-8") 460 | headWriter.write("delete") 461 | headWriter.write(' ') 462 | val key @ KeyRegex() = accessor.key 463 | headWriter.write(key) 464 | headWriter.write("\r\n") 465 | headWriter.flush() 466 | logger.fine("DELETE send head: " + headOutputStream.toString("UTF-8")) 467 | sendAndReceive[Unit]( 468 | socket, 469 | Array(ByteBuffer.wrap(headOutputStream.toByteArray))) { inputStream => 470 | val line = inputStream.readUntilCRLF() 471 | line match { 472 | case ServerErrorRegex(reason) => 473 | raise(new ServerErrorException(reason)) 474 | case "NOT_FOUND\r\n" => 475 | raise(new NotFoundException) 476 | case "DELETED\r\n" => 477 | shiftUnit0[Unit, Unit]() 478 | case _ => 479 | raise(new ProtocolException("Bad line " + line)) 480 | } 481 | } 482 | } 483 | 484 | // TODO: store with "noreplay" parameter 485 | 486 | final def newSocket( 487 | group: AsynchronousChannelGroup, 488 | address: SocketAddress)( 489 | implicit catcher: Catcher[Unit]): AsynchronousSocketChannel @suspendable = { 490 | val socket = AsynchronousSocketChannel.open(group) 491 | socket.setOption( 492 | StandardSocketOptions.SO_KEEPALIVE, 493 | java.lang.Boolean.TRUE) 494 | socket.setOption( 495 | StandardSocketOptions.TCP_NODELAY, 496 | java.lang.Boolean.TRUE) 497 | shift { (continue: Void => Unit) => 498 | socket.connect( 499 | address, 500 | (continue, catcher), 501 | ContinuationizedCompletionHandler.VoidHandler) 502 | } 503 | socket 504 | } 505 | } 506 | 507 | // vim: set sts=2 sw=2 et: 508 | -------------------------------------------------------------------------------- /src/main/scala/com/dongxiguo/memcontinuationed/Memcontinuationed.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013,2014 深圳市葡萄藤网络科技有限公司 (Shenzhen Putaoteng Network Technology Co., Ltd.) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dongxiguo.memcontinuationed 18 | 19 | import com.dongxiguo.commons.continuations._ 20 | import com.dongxiguo.commons.continuations.CollectionConverters._ 21 | import com.dongxiguo.commons.continuations.io._ 22 | import java.nio.channels._ 23 | import java.net._ 24 | import java.util.concurrent.atomic._ 25 | import java.util.Arrays 26 | import java.util.Collections 27 | import scala.annotation.tailrec 28 | import scala.util.control.Exception.Catcher 29 | import scala.util.continuations._ 30 | import scala.collection.JavaConverters._ 31 | import com.dongxiguo.fastring.Fastring.Implicits._ 32 | 33 | private[memcontinuationed] // Workaround for https://issues.scala-lang.org/browse/SI-6585 34 | object Memcontinuationed { 35 | private implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this) 36 | 37 | private sealed abstract class Command 38 | 39 | private sealed abstract class OtherCommand( 40 | val continue: Unit => Unit)( 41 | implicit val catcher: Catcher[Unit]) extends Command { 42 | def apply(socket: AsynchronousSocketChannel)( 43 | implicit catcher: Catcher[Unit]): Unit @suspendable 44 | } 45 | 46 | private sealed abstract class GetOrGetsCommand extends Command 47 | 48 | private type GetCallback[Value] = Tuple2[Array[StorageAccessor[Value]], Array[Value]] => Unit 49 | 50 | private final class GetCommand[Value]( 51 | val accessors: Array[StorageAccessor[Value]], 52 | val continue: GetCallback[Value])( 53 | implicit val catcher: Catcher[Unit]) 54 | extends GetOrGetsCommand 55 | 56 | private type GetsCallback[Value] = Tuple3[Array[StorageAccessor[Value]], Array[Value], Array[CasId]] => Unit 57 | 58 | private final class GetsCommand[Value]( 59 | val accessors: Array[StorageAccessor[Value]], 60 | val continue: GetsCallback[Value])( 61 | implicit val catcher: Catcher[Unit]) 62 | extends GetOrGetsCommand 63 | 64 | private case object CleanUpCommand extends Command 65 | 66 | private final class CommandQueueBuilder( 67 | var getCommands: List[GetCommand[_]] = Nil, 68 | var getsCommands: List[GetsCommand[_]] = Nil, 69 | var otherCommands: List[OtherCommand] = Nil, 70 | var waitingCleanUp: Boolean = false) 71 | extends collection.mutable.Builder[Command, CommandQueue] { 72 | 73 | override final def ++=(that: TraversableOnce[Command]): this.type = { 74 | that match { 75 | case cq: CommandQueue => 76 | getCommands = getCommands reverse_::: cq.getCommands 77 | getsCommands = getsCommands reverse_::: cq.getsCommands 78 | otherCommands = otherCommands reverse_::: cq.otherCommands 79 | waitingCleanUp ||= cq.isInstanceOf[WaitingCleanUpCommandQueue] 80 | this 81 | case _ => 82 | super.++=(that) 83 | } 84 | } 85 | 86 | override final def +=(elem: Command): this.type = { 87 | elem match { 88 | case getCommand: GetCommand[_] => 89 | getCommands = getCommand :: getCommands 90 | case getsCommand: GetsCommand[_] => 91 | getsCommands = getsCommand :: getsCommands 92 | case otherCommand: OtherCommand => 93 | otherCommands = otherCommand :: otherCommands 94 | case CleanUpCommand => 95 | waitingCleanUp = true 96 | } 97 | this 98 | } 99 | 100 | override final def clear() { 101 | getCommands = Nil 102 | getsCommands = Nil 103 | otherCommands = Nil 104 | waitingCleanUp = false 105 | } 106 | override final def result = 107 | if (waitingCleanUp) { 108 | new CommandQueue(getCommands, getsCommands, otherCommands) 109 | } else { 110 | new WaitingCleanUpCommandQueue(getCommands, getsCommands, otherCommands) 111 | } 112 | } 113 | 114 | private class CommandQueue( 115 | val getCommands: List[GetCommand[_]], 116 | val getsCommands: List[GetsCommand[_]], 117 | val otherCommands: List[OtherCommand]) 118 | extends Traversable[Command] with collection.TraversableLike[Command, CommandQueue] { 119 | override def foreach[U](f: Command => U) { 120 | otherCommands.foreach(f) 121 | getCommands.foreach(f) 122 | getsCommands.foreach(f) 123 | } 124 | 125 | override protected final def newBuilder = new CommandQueueBuilder 126 | 127 | override final def seq = this 128 | } 129 | 130 | private final class WaitingCleanUpCommandQueue( 131 | getCommands: List[GetCommand[_]], 132 | getsCommands: List[GetsCommand[_]], 133 | otherCommands: List[OtherCommand]) 134 | extends CommandQueue(getCommands, getsCommands, otherCommands) { 135 | override final def foreach[U](f: Command => U) { 136 | super.foreach(f) 137 | f(CleanUpCommand) 138 | } 139 | } 140 | 141 | } 142 | 143 | /** Connections to a memcached server cluster. 144 | * 145 | * @param group the channel group used to create socket to memcached server 146 | * @param locator the function decide where the server is for specified key 147 | * (you may implement ketama hashing here) 148 | */ 149 | final class Memcontinuationed( 150 | group: AsynchronousChannelGroup, 151 | locator: StorageAccessor[_] => SocketAddress) { 152 | import Memcontinuationed._ 153 | 154 | // Compile error in Scala 2.11.0, I don't know why. 155 | // logger.fine("Creating Memcontinuationed...") 156 | 157 | private final class CommandRunner(address: SocketAddress) 158 | extends SequentialRunner[Command, CommandQueue] { 159 | 160 | @volatile private var _socket: AsynchronousSocketChannel = null 161 | 162 | // 获取socket,如果未连接,则自动重连 163 | def socket(implicit catcher: Catcher[Unit]): AsynchronousSocketChannel @suspendable = { 164 | if (_socket == null || !_socket.isOpen) { 165 | _socket = AsciiProtocol.newSocket(group, address) 166 | _socket 167 | } else { 168 | _socket 169 | } 170 | } 171 | 172 | @inline private def forceContinue[A, B, C]( 173 | command: GetsCommand[A], 174 | accessors: Array[StorageAccessor[B]], 175 | values: Array[C], 176 | casIDs: Array[CasId]) { 177 | command.continue( 178 | accessors.asInstanceOf[Array[StorageAccessor[A]]], 179 | values.asInstanceOf[Array[A]], 180 | casIDs) 181 | } 182 | 183 | @inline private def forceContinue[A, B, C]( 184 | command: GetCommand[A], 185 | accessors: Array[StorageAccessor[B]], 186 | values: Array[C]) { 187 | command.continue( 188 | accessors.asInstanceOf[Array[StorageAccessor[A]]], 189 | values.asInstanceOf[Array[A]]) 190 | } 191 | 192 | private def runGet(tasks: CommandQueue)(implicit catcher: Catcher[Unit]) = { 193 | val getAccessorIterator = 194 | tasks.getCommands.iterator.flatMap[StorageAccessor[_]] { 195 | _.accessors 196 | } 197 | val accessors = getAccessorIterator.toArray 198 | 199 | // Workaround for https://issues.scala-lang.org/browse/SI-6588 200 | AsciiProtocol.get(socket, accessors: _*) match { 201 | case (responseAccessors, values) => { 202 | for (getCommand <- tasks.getCommands) { 203 | forceContinue(getCommand, responseAccessors, values) 204 | } 205 | } 206 | } 207 | } 208 | 209 | private def runGets( 210 | tasks: CommandQueue)( 211 | implicit catcher: Catcher[Unit]) = { 212 | val getAccessorIterator = 213 | tasks.getCommands.iterator.flatMap[StorageAccessor[_]] { 214 | _.accessors 215 | } 216 | val getsAccessorIterator = 217 | tasks.getsCommands.iterator.flatMap[StorageAccessor[_]] { 218 | _.accessors 219 | } 220 | val accessors = (getAccessorIterator ++ getsAccessorIterator).toArray 221 | 222 | AsciiProtocol.gets(socket, accessors: _*) match { 223 | case (responseAccessors, values, casIDs) => { 224 | for (getCommand <- tasks.getCommands) { 225 | forceContinue(getCommand, responseAccessors, values) 226 | } 227 | for (getsCommand <- tasks.getsCommands) { 228 | forceContinue(getsCommand, responseAccessors, values, casIDs) 229 | } 230 | } 231 | } 232 | } 233 | 234 | private def runOther( 235 | command: OtherCommand)( 236 | implicit catcher: Catcher[Unit]): Unit @suspendable = { 237 | logger.finest("Before OtherCommand.apply()") 238 | command(socket) 239 | logger.finest("After OtherCommand.apply()") 240 | command.continue() 241 | } 242 | 243 | override protected final def consumeSome(tasks: CommandQueue): CommandQueue @suspendable = { 244 | tasks.otherCommands match { 245 | case Nil => { 246 | if (tasks.isInstanceOf[WaitingCleanUpCommandQueue] && 247 | tasks.getCommands.isEmpty && tasks.getsCommands.isEmpty) { 248 | if (_socket != null) { 249 | _socket.close() 250 | _socket = null 251 | } 252 | new CommandQueue(Nil, Nil, Nil) 253 | } else { 254 | shift { (continue: CommandQueue => Unit) => 255 | implicit val catcher: Catcher[Unit] = { 256 | case e: Exception => 257 | for (getsCommand <- tasks.getsCommands) { 258 | if (getsCommand.catcher.isDefinedAt(e)) { 259 | getsCommand.catcher(e) 260 | } 261 | } 262 | for (getCommand <- tasks.getCommands) { 263 | if (getCommand.catcher.isDefinedAt(e)) { 264 | getCommand.catcher(e) 265 | } 266 | } 267 | // 如果不是逻辑错误,则杀掉连接 268 | if (!e.isInstanceOf[LogicException] && _socket != null) { 269 | _socket.close() 270 | _socket = null 271 | } 272 | continue(new CommandQueue(Nil, Nil, tasks.otherCommands)) 273 | } 274 | reset { 275 | if (tasks.getsCommands.nonEmpty) { 276 | runGets(tasks) 277 | } else { 278 | runGet(tasks) 279 | } 280 | continue(new CommandQueue(Nil, Nil, tasks.otherCommands)) 281 | } 282 | } 283 | } 284 | } 285 | case otherCommands => { 286 | logger.fine { 287 | fast"${otherCommands.size} OtherCommands remainning." 288 | } 289 | def command = otherCommands.head 290 | shift { (continue: CommandQueue => Unit) => 291 | implicit val catcher: Catcher[Unit] = { 292 | case e: Exception => 293 | if (command.catcher.isDefinedAt(e)) { 294 | command.catcher(e) 295 | } 296 | // 如果不是逻辑错误,则杀掉连接 297 | if (!e.isInstanceOf[LogicException] && _socket != null) { 298 | _socket.close() 299 | _socket = null 300 | } 301 | continue( 302 | new CommandQueue( 303 | tasks.getCommands, 304 | tasks.getsCommands, 305 | otherCommands.tail)) 306 | } 307 | reset[Unit, Unit] { 308 | runOther(command) 309 | continue( 310 | new CommandQueue( 311 | tasks.getCommands, 312 | tasks.getsCommands, 313 | otherCommands.tail)) 314 | } 315 | } 316 | } 317 | } 318 | } 319 | 320 | override protected final def taskQueueCanBuildFrom = 321 | new collection.generic.CanBuildFrom[CommandQueue, Command, CommandQueue] { 322 | def apply() = new CommandQueueBuilder 323 | def apply(from: CommandQueue) = new CommandQueueBuilder() 324 | } 325 | } 326 | 327 | private val runnerMapReference = 328 | new AtomicReference(Map.empty[SocketAddress, CommandRunner]) 329 | 330 | private final class ShuttedDown extends Map[SocketAddress, CommandRunner] { 331 | override final def +[B1 >: CommandRunner](kv: (SocketAddress, B1)) = 332 | throw new IllegalStateException("Memcontinuationed is shutted down.") 333 | 334 | override final def -(key: SocketAddress) = 335 | throw new IllegalStateException("Memcontinuationed is shutted down.") 336 | 337 | override final def get(key: SocketAddress) = 338 | throw new IllegalStateException("Memcontinuationed is shutted down.") 339 | 340 | override final def iterator = 341 | throw new IllegalStateException("Memcontinuationed is shutted down.") 342 | 343 | } 344 | 345 | /* @tailrec */ // FIXME: Workaround for https://issues.scala-lang.org/browse/SI-6589 346 | private def getCommandRunner( 347 | address: SocketAddress)( 348 | implicit catcher: Catcher[Unit]): CommandRunner @suspendable = { 349 | val runnerMap = runnerMapReference.get 350 | if (runnerMap.isInstanceOf[ShuttedDown]) { 351 | val e = new ShuttedDownException("Memcontinuationed is shutted down.") 352 | if (catcher.isDefinedAt(e)) { 353 | catcher(e) 354 | } else { 355 | throw e 356 | } 357 | shift(Hang) 358 | } else { 359 | runnerMap.get(address) match { 360 | case None => 361 | val runner = new CommandRunner(address) 362 | val newRunnerMap: Map[SocketAddress, CommandRunner] = runnerMap.updated(address, runner) 363 | if (runnerMapReference.compareAndSet(runnerMap, newRunnerMap)) { 364 | runner 365 | } else { 366 | // retry 367 | getCommandRunner(address) 368 | } 369 | case Some(runner) => 370 | runner 371 | } 372 | } 373 | } 374 | 375 | private def getCommandRunnerForAccessor( 376 | accessor: StorageAccessor[_])( 377 | implicit catcher: Catcher[Unit]) = 378 | getCommandRunner(locator(accessor)) 379 | 380 | /** Starts to shut down all connections to server and returns immediately. 381 | * 382 | * Later request to this `Memcontinuationed` will be rejected. 383 | * All pending requests will not be terminated, 384 | * and the responses for these requests will be handled as usual. 385 | */ 386 | final def shutdown() { 387 | runnerMapReference.getAndSet(new ShuttedDown).values foreach { 388 | _.shutDown(CleanUpCommand) 389 | } 390 | } 391 | 392 | @throws(classOf[ProtocolException]) 393 | @throws(classOf[ExistsException]) 394 | @throws(classOf[NotStoredException]) 395 | @throws(classOf[NotFoundException]) 396 | @throws(classOf[ServerErrorException]) 397 | private def store[Value](command: String)( 398 | accessor: StorageAccessor[Value], 399 | value: Value, 400 | exptime: Exptime = Exptime.NeverExpires)( 401 | implicit catcher: Catcher[Unit]): Unit @suspendable = { 402 | val commandRunner = getCommandRunnerForAccessor(accessor) 403 | shift { (continue: Unit => Unit) => 404 | logger.finer("Enqueue store command.") 405 | try { 406 | commandRunner.enqueue(new OtherCommand(continue) { 407 | override final def apply( 408 | socket: AsynchronousSocketChannel)( 409 | implicit catcher: Catcher[Unit]): Unit @suspendable = { 410 | AsciiProtocol.store(socket, command, accessor, value, exptime) 411 | } 412 | }) 413 | commandRunner.flush() 414 | } catch { 415 | case e if catcher.isDefinedAt(e) => 416 | catcher(e) 417 | } 418 | } 419 | } 420 | 421 | /** Send a `set` request to server. 422 | * 423 | * @param accessor the key sent to server. 424 | * @param value the new value sent to server. 425 | * @param exptime the expiration time sent to server. 426 | * @param catcher the catcher to handle exceptions. 427 | */ 428 | @throws(classOf[ProtocolException]) 429 | @throws(classOf[ExistsException]) 430 | @throws(classOf[NotStoredException]) 431 | @throws(classOf[NotFoundException]) 432 | @throws(classOf[ServerErrorException]) 433 | final def set[Value]( 434 | accessor: StorageAccessor[Value], 435 | value: Value, 436 | exptime: Exptime = Exptime.NeverExpires)( 437 | implicit catcher: Catcher[Unit]): Unit @suspendable = 438 | store("set")(accessor, value, exptime) 439 | 440 | /** Send an `add` request to server. 441 | * 442 | * @param accessor the key sent to server. 443 | * @param value the new value sent to server. 444 | * @param exptime the expiration time sent to server. 445 | * @param catcher the catcher to handle exceptions. 446 | */ 447 | @throws(classOf[ProtocolException]) 448 | @throws(classOf[ExistsException]) 449 | @throws(classOf[NotStoredException]) 450 | @throws(classOf[NotFoundException]) 451 | @throws(classOf[ServerErrorException]) 452 | final def add[Value]( 453 | accessor: StorageAccessor[Value], 454 | value: Value, 455 | exptime: Exptime = Exptime.NeverExpires)( 456 | implicit catcher: Catcher[Unit]): Unit @suspendable = 457 | store("add")(accessor, value, exptime) 458 | 459 | /** Send a `replace` request to server. 460 | * 461 | * @param accessor the key sent to server. 462 | * @param value the new value sent to server. 463 | * @param exptime the expiration time sent to server. 464 | * @param catcher the catcher to handle exceptions. 465 | */ 466 | @throws(classOf[ProtocolException]) 467 | @throws(classOf[ExistsException]) 468 | @throws(classOf[NotStoredException]) 469 | @throws(classOf[NotFoundException]) 470 | @throws(classOf[ServerErrorException]) 471 | final def replace[Value]( 472 | accessor: StorageAccessor[Value], 473 | value: Value, 474 | exptime: Exptime = Exptime.NeverExpires)( 475 | implicit catcher: Catcher[Unit]): Unit @suspendable = 476 | store("replace")(accessor, value, exptime) 477 | 478 | /** Send an `append` request to server. 479 | * 480 | * @param accessor the key sent to server. 481 | * @param value the data to be append. 482 | * @param exptime the expiration time sent to server. 483 | * @param catcher the catcher to handle exceptions. 484 | */ 485 | @throws(classOf[ProtocolException]) 486 | @throws(classOf[ExistsException]) 487 | @throws(classOf[NotStoredException]) 488 | @throws(classOf[NotFoundException]) 489 | @throws(classOf[ServerErrorException]) 490 | final def append[Value]( 491 | accessor: StorageAccessor[Value], 492 | value: Value, 493 | exptime: Exptime = Exptime.NeverExpires)( 494 | implicit catcher: Catcher[Unit]): Unit @suspendable = 495 | store("append")(accessor, value, exptime) 496 | 497 | /** Send a `prepend` request to server. 498 | * 499 | * @param accessor the key sent to server. 500 | * @param value the data to be prepend. 501 | * @param exptime the expiration time sent to server. 502 | * @param catcher the catcher to handle exceptions. 503 | */ 504 | @throws(classOf[ProtocolException]) 505 | @throws(classOf[ExistsException]) 506 | @throws(classOf[NotStoredException]) 507 | @throws(classOf[NotFoundException]) 508 | @throws(classOf[ServerErrorException]) 509 | final def prepend[Value]( 510 | accessor: StorageAccessor[Value], 511 | value: Value, 512 | exptime: Exptime = Exptime.NeverExpires)( 513 | implicit catcher: Catcher[Unit]): Unit @suspendable = 514 | store("prepend")(accessor, value, exptime) 515 | 516 | /** An key/value pair that can be modified. 517 | * 518 | * @param origin the origin value before you modify it. 519 | * @param accessor the key 520 | */ 521 | final class Mutable[Value] private[Memcontinuationed] ( 522 | val accessor: StorageAccessor[Value], 523 | val origin: Value, 524 | casID: CasId) { 525 | 526 | override final def toString = 527 | "Mutable(key=" + accessor.key + 528 | ",value=" + origin + 529 | "casID=" + casID + ")" 530 | 531 | /** Send a `cas` request to server. 532 | * 533 | * @param newValue the new value sent to server. 534 | * @param exptime the expiration time sent to server. 535 | * @param catcher the catcher to handle exceptions. 536 | * 537 | * @note If your `mutator` is not `@suspendable`, use [[#casUntilSuccess]] instead. 538 | */ 539 | @throws(classOf[ProtocolException]) 540 | @throws(classOf[ExistsException]) 541 | @throws(classOf[NotStoredException]) 542 | @throws(classOf[NotFoundException]) 543 | @throws(classOf[ServerErrorException]) 544 | final def cas(newValue: Value, exptime: Exptime = Exptime.NeverExpires)( 545 | implicit catcher: Catcher[Unit]): Unit @suspendable = { 546 | val commandRunner = getCommandRunnerForAccessor(accessor) 547 | shift { (continue: Unit => Unit) => 548 | logger.finer("Enqueue CAS command.") 549 | try { 550 | commandRunner.enqueue(new OtherCommand(continue) { 551 | override final def apply( 552 | socket: AsynchronousSocketChannel)( 553 | implicit catcher: Catcher[Unit]): Unit @suspendable = { 554 | AsciiProtocol.cas(socket, accessor, newValue, casID, exptime) 555 | } 556 | }) 557 | commandRunner.flush() 558 | } catch { 559 | case e if catcher.isDefinedAt(e) => 560 | catcher(e) 561 | } 562 | } 563 | } 564 | 565 | /** 566 | * @param mutator 如果mutator的返回值和origin相等, 567 | * 则不用存数据而直接成功 568 | */ 569 | @throws(classOf[ProtocolException]) 570 | @throws(classOf[NotStoredException]) 571 | @throws(classOf[NotFoundException]) 572 | @throws(classOf[ServerErrorException]) 573 | private def asyncContinuationizedCASUntilSuccess( 574 | mutator: Value => Value @suspendable, 575 | exptime: Exptime = Exptime.NeverExpires)( 576 | continue: Unit => Unit)( 577 | implicit catcher: Catcher[Unit], 578 | valueManifest: Manifest[Value]) { 579 | reset { 580 | val newValue = mutator(origin) 581 | if (newValue != origin) { 582 | cas(newValue, exptime) { 583 | case e: ExistsException => 584 | reset { 585 | val newMutableMap = gets(accessor) 586 | newMutableMap.get(accessor) match { 587 | case None => 588 | val notFoundException = new NotFoundException 589 | if (!catcher.isDefinedAt(notFoundException)) { 590 | throw notFoundException 591 | } 592 | catcher(notFoundException) 593 | case Some(newMutable) => 594 | newMutable.asyncContinuationizedCASUntilSuccess( 595 | mutator, 596 | exptime)(continue) 597 | } 598 | } 599 | case e if catcher.isDefinedAt(e) => 600 | catcher(e) 601 | } 602 | } else shiftUnit0[Unit, Unit]() 603 | continue() 604 | } 605 | } 606 | 607 | /** Modifies the value and send [[#cas]] request to server. 608 | * 609 | * If the value is changed, retry. 610 | * 611 | * @param mutator the function that modifies the value. 612 | * @param exptime the expiration time sent to server. 613 | * @param catcher the catcher to handle exceptions. 614 | * @param valueManifest the manifest for `Value`. 615 | * 616 | * @note If your `mutator` is not `@suspendable`, use [[#casUntilSuccess]] instead. 617 | */ 618 | @throws(classOf[ProtocolException]) 619 | @throws(classOf[NotStoredException]) 620 | @throws(classOf[NotFoundException]) 621 | @throws(classOf[ServerErrorException]) 622 | final def continuationizedCASUntilSuccess( 623 | mutator: Value => Value @suspendable, 624 | exptime: Exptime = Exptime.NeverExpires)( 625 | implicit catcher: Catcher[Unit], 626 | valueManifest: Manifest[Value]): Unit @suspendable = { 627 | shift { (continue: Unit => Unit) => 628 | asyncContinuationizedCASUntilSuccess(mutator, exptime)(continue) 629 | } 630 | } 631 | 632 | /** 633 | * @param mutator 如果mutator的返回值和origin相等, 634 | * 则不用存数据而直接成功 635 | */ 636 | @throws(classOf[ProtocolException]) 637 | @throws(classOf[NotStoredException]) 638 | @throws(classOf[NotFoundException]) 639 | @throws(classOf[ServerErrorException]) 640 | private def asyncCASUntilSuccess( 641 | mutator: Value => Value, 642 | exptime: Exptime = Exptime.NeverExpires)( 643 | continue: Unit => Unit)( 644 | implicit catcher: Catcher[Unit], 645 | valueManifest: Manifest[Value]) { 646 | reset { 647 | val newValue = mutator(origin) 648 | if (newValue != origin) { 649 | cas(newValue, exptime) { 650 | case e: ExistsException => 651 | reset { 652 | val newMutableMap = gets(accessor) 653 | newMutableMap.get(accessor) match { 654 | case None => 655 | val notFoundException = new NotFoundException 656 | if (!catcher.isDefinedAt(notFoundException)) { 657 | throw notFoundException 658 | } 659 | catcher(notFoundException) 660 | case Some(newMutable) => 661 | newMutable.asyncCASUntilSuccess( 662 | mutator, 663 | exptime)(continue) 664 | } 665 | } 666 | case e if catcher.isDefinedAt(e) => 667 | catcher(e) 668 | } 669 | } else shiftUnit0[Unit, Unit]() 670 | continue() 671 | } 672 | } 673 | 674 | /** Modifies the value and send [[#cas]] request to server. 675 | * 676 | * If the value is changed, retry. 677 | * 678 | * @param mutator the function that modifies the value. 679 | * @param exptime the expiration time sent to server. 680 | * @param catcher the catcher to handle exceptions. 681 | * @param valueManifest the manifest for `Value`. 682 | * 683 | * @note If your `mutator` is `@suspendable`, use [[#continuationizedCASUntilSuccess]] instead. 684 | */ 685 | @throws(classOf[ProtocolException]) 686 | @throws(classOf[NotStoredException]) 687 | @throws(classOf[NotFoundException]) 688 | @throws(classOf[ServerErrorException]) 689 | final def casUntilSuccess( 690 | mutator: Value => Value, 691 | exptime: Exptime = Exptime.NeverExpires)( 692 | implicit catcher: Catcher[Unit], 693 | valueManifest: Manifest[Value]): Unit @suspendable = { 694 | shift { (continue: Unit => Unit) => 695 | asyncCASUntilSuccess(mutator, exptime)(continue) 696 | } 697 | } 698 | } 699 | 700 | /** Send an `add` request to server, and if there is that key on server, send an `append` request instead. 701 | * 702 | * @param accessor the key sent to server. 703 | * @param value the new value sent to server. 704 | * @param exptime the expiration time sent to server. 705 | * @param catcher the catcher to handle exceptions. 706 | */ 707 | @throws(classOf[ProtocolException]) 708 | @throws(classOf[ServerErrorException]) 709 | final def addOrAppend[Value]( 710 | accessor: StorageAccessor[Value], 711 | value: Value, 712 | exptime: Exptime = Exptime.NeverExpires)( 713 | implicit catcher: Catcher[Unit]): Unit @suspendable = { 714 | shift { (continue: Unit => Unit) => 715 | reset { 716 | add(accessor, value, exptime) { 717 | case e: NotStoredException => 718 | reset { 719 | append(accessor, value, exptime) 720 | continue() 721 | } 722 | case e if catcher.isDefinedAt(e) => 723 | catcher(e) 724 | } 725 | continue() 726 | } 727 | } 728 | } 729 | 730 | private[Memcontinuationed] final class GetsResult[Value]( 731 | allRequestedAccessors: Array[StorageAccessor[Value]], 732 | resultsByAddress: Map[SocketAddress, (Array[StorageAccessor[Value]], Array[Value], Array[CasId])]) 733 | extends Map[StorageAccessor[Value], Mutable[Value]] { 734 | 735 | // Compile error in Scala 2.11.0, I don't know why. 736 | // logger.finest(fast"Create new GetsResult $this") 737 | 738 | override final def iterator = { 739 | import language.postfixOps 740 | for { 741 | accessor <- allRequestedAccessors.iterator 742 | address = locator(accessor) 743 | (accessors, values, casIDs) = resultsByAddress(address) 744 | i = Collections.binarySearch( 745 | accessors.view.map { _.key } asJava, 746 | accessor.key) 747 | if i >= 0 748 | value = values(i) 749 | if value != null 750 | } yield accessor -> new Mutable[Value](accessor, value, casIDs(i)) 751 | } 752 | 753 | override final def +[B1 >: Mutable[Value]]( 754 | kv: (StorageAccessor[Value], B1)): Map[StorageAccessor[Value], B1] = 755 | (iterator ++ Seq(kv)).toMap 756 | 757 | override final def -( 758 | accessor: StorageAccessor[Value]): Map[StorageAccessor[Value], Mutable[Value]] = { 759 | import language.postfixOps 760 | iterator filter { _._1.key != accessor.key } toMap 761 | } 762 | 763 | override final def get( 764 | accessor: StorageAccessor[Value]): Option[Mutable[Value]] = { 765 | val address = locator(accessor) 766 | val (accessors, values, casIDs) = resultsByAddress(address) 767 | import language.postfixOps 768 | val i = Collections.binarySearch( 769 | accessors.view map { _.key } asJava, 770 | accessor.key) 771 | if (i < 0 || 772 | Arrays.binarySearch[StorageAccessor[Value]]( 773 | allRequestedAccessors, 774 | accessor, 775 | StorageAccessorComparator) < 0) { 776 | None 777 | } else { 778 | assert(accessors(i) == accessor) 779 | values(i) match { 780 | case null => 781 | None 782 | case value => 783 | Some(new Mutable(accessors(i), value, casIDs(i))) 784 | } 785 | } 786 | } 787 | } 788 | 789 | /** Returns key/value pairs by `allAccessors` for update. 790 | * 791 | * If some requested keys does not exist, the returned pairs may be less than requested keys. 792 | * 793 | * @param allAccessors the requested keys. 794 | * @param catcher the catcher to handle exceptions. 795 | * @param valueManifest the manifest for `Value`. 796 | */ 797 | @throws(classOf[ProtocolException]) 798 | @throws(classOf[ServerErrorException]) 799 | final def gets[Value](allAccessors: StorageAccessor[Value]*)( 800 | implicit catcher: Catcher[Unit], valueManifest: Manifest[Value]): Map[StorageAccessor[Value], Mutable[Value]] @suspendable = { 801 | val groupedAccessors = allAccessors.groupBy(locator) 802 | val results = groupedAccessors.asSuspendable.par map { entry => 803 | val (address, accessors) = entry 804 | val commandRunner = getCommandRunner(address) 805 | val result = shift { continue: GetsCallback[Value] => 806 | try { 807 | commandRunner.enqueue(new GetsCommand[Value](accessors.toArray, continue)) 808 | commandRunner.flush() 809 | } catch { 810 | case e if catcher.isDefinedAt(e) => 811 | catcher(e) 812 | } 813 | } 814 | address -> result 815 | } 816 | val sortedAccessors = allAccessors.toArray 817 | Arrays.sort[StorageAccessor[Value]]( 818 | sortedAccessors, 819 | StorageAccessorComparator) 820 | new GetsResult(sortedAccessors, results.toMap) 821 | } 822 | 823 | /** Returns one value by the `accessor` for update. 824 | * 825 | * @param accessor the key sent to server 826 | * @param valueManifest the manifest for `Value`. 827 | * @throws com.dongxiguo.memcontinuationed.NotFoundException if the key does not exist 828 | * 829 | * @note If you need to fetch multiply values, use [[#gets]] instead. 830 | */ 831 | @throws(classOf[ProtocolException]) 832 | @throws(classOf[ServerErrorException]) 833 | @throws(classOf[NotFoundException]) 834 | final def requireForCAS[Value](accessor: StorageAccessor[Value])( 835 | implicit catcher: Catcher[Unit], valueManifest: Manifest[Value]): Mutable[Value] @suspendable = { 836 | val address = locator(accessor) 837 | val commandRunner = getCommandRunner(address) 838 | shift { (continue: Mutable[Value] => Unit) => 839 | try { 840 | commandRunner.enqueue( 841 | new GetsCommand[Value]( 842 | Array(accessor), 843 | { result: (Array[StorageAccessor[Value]], Array[Value], Array[CasId]) => 844 | val (accessors, values, casIDs) = result 845 | import language.postfixOps 846 | val i = Collections.binarySearch( 847 | accessors.view map { _.key } asJava, 848 | accessor.key) 849 | if (i < 0) { 850 | val e = new NotFoundException 851 | if (catcher.isDefinedAt(e)) { 852 | catcher(e) 853 | } else { 854 | throw e 855 | } 856 | } else { 857 | values(i) match { 858 | case null => 859 | val e = new NotFoundException 860 | if (catcher.isDefinedAt(e)) { 861 | catcher(e) 862 | } else { 863 | throw e 864 | } 865 | case value => 866 | continue(new Mutable[Value](accessor, value, casIDs(i))) 867 | } 868 | } 869 | })) 870 | commandRunner.flush() 871 | } catch { 872 | case e if catcher.isDefinedAt(e) => 873 | catcher(e) 874 | } 875 | } 876 | } 877 | 878 | private[Memcontinuationed] final class GetResult[Value]( 879 | allRequestedAccessors: Array[StorageAccessor[Value]], 880 | resultsByAddress: Map[SocketAddress, (Array[StorageAccessor[Value]], Array[Value])]) 881 | extends Map[StorageAccessor[Value], Value] { 882 | override final def iterator = { 883 | import language.postfixOps 884 | for { 885 | accessor <- allRequestedAccessors.iterator 886 | address = locator(accessor) 887 | (accessors, values) = resultsByAddress(address) 888 | i = Collections.binarySearch( 889 | accessors.view.map { _.key } asJava, 890 | accessor.key) 891 | if i >= 0 892 | value = values(i) 893 | if value != null 894 | } yield accessor -> value 895 | } 896 | 897 | override final def +[B1 >: Value]( 898 | kv: (StorageAccessor[Value], B1)): Map[StorageAccessor[Value], B1] = 899 | (iterator ++ Seq(kv)).toMap 900 | 901 | override final def -( 902 | accessor: StorageAccessor[Value]): Map[StorageAccessor[Value], Value] = { 903 | import language.postfixOps 904 | iterator filter { _._1.key != accessor.key } toMap 905 | } 906 | 907 | override final def get( 908 | accessor: StorageAccessor[Value]): Option[Value] = { 909 | val address = locator(accessor) 910 | val (accessors, values) = resultsByAddress(address) 911 | import language.postfixOps 912 | val i = Collections.binarySearch( 913 | accessors.view map { _.key } asJava, 914 | accessor.key) 915 | if (i < 0 || 916 | Arrays.binarySearch[StorageAccessor[Value]]( 917 | allRequestedAccessors, 918 | accessor, 919 | StorageAccessorComparator) < 0) { 920 | None 921 | } else { 922 | assert(accessors(i) == accessor) 923 | Option(values(i)) 924 | } 925 | } 926 | } 927 | 928 | /** Returns key/value pairs by `allAccessors`. 929 | * 930 | * If some requested keys does not exist, the returned pairs may be less than requested keys. 931 | * 932 | * @param allAccessors the requested keys. 933 | * @param catcher the catcher to handle exceptions. 934 | * @param valueManifest the manifest for `Value`. 935 | */ 936 | @throws(classOf[ProtocolException]) 937 | @throws(classOf[ServerErrorException]) 938 | final def get[Value](allAccessors: StorageAccessor[Value]*)( 939 | implicit catcher: Catcher[Unit], valueManifest: Manifest[Value]): Map[StorageAccessor[Value], Value] @suspendable = { 940 | logger.finer("get") 941 | val groupedAccessors = allAccessors.groupBy(locator) 942 | val results = groupedAccessors.asSuspendable.par map { entry => 943 | val (address, accessors) = entry 944 | logger.finer("getCommandRunner") 945 | val commandRunner = getCommandRunner(address) 946 | val result = shift { continue: GetCallback[Value] => 947 | logger.finer("enqueue") 948 | try { 949 | commandRunner.enqueue(new GetCommand[Value](accessors.toArray, continue)) 950 | commandRunner.flush() 951 | } catch { 952 | case e if catcher.isDefinedAt(e) => 953 | catcher(e) 954 | } 955 | } 956 | address -> result 957 | } 958 | val sortedAccessors = allAccessors.toArray 959 | Arrays.sort[StorageAccessor[Value]]( 960 | sortedAccessors, 961 | StorageAccessorComparator) 962 | new GetResult(sortedAccessors, results.toMap) 963 | } 964 | 965 | /** Returns one value by `accessor`. 966 | * 967 | * @param accessor the requested key. 968 | * @param catcher the catcher to handle exceptions. 969 | * @param valueManifest the manifest for `Value`. 970 | * @throws com.dongxiguo.memcontinuationed.NotFoundException if the key does not exist. 971 | * @note If you need to fetch more than one value, use [[#get]] instead. 972 | */ 973 | @throws(classOf[ProtocolException]) 974 | @throws(classOf[ServerErrorException]) 975 | @throws(classOf[NotFoundException]) 976 | final def require[Value](accessor: StorageAccessor[Value])( 977 | implicit catcher: Catcher[Unit], valueManifest: Manifest[Value]): Value @suspendable = { 978 | logger.fine("REQUIRE " + accessor) 979 | val address = locator(accessor) 980 | val commandRunner = getCommandRunner(address) 981 | shift { (continue: Value => Unit) => 982 | try { 983 | commandRunner.enqueue( 984 | new GetCommand[Value]( 985 | Array(accessor), 986 | { result: (Array[StorageAccessor[Value]], Array[Value]) => 987 | val (accessors, values) = result 988 | import language.postfixOps 989 | val i = Collections.binarySearch( 990 | accessors.view map { _.key } asJava, 991 | accessor.key) 992 | if (i < 0) { 993 | val e = new NotFoundException 994 | if (catcher.isDefinedAt(e)) { 995 | catcher(e) 996 | } else { 997 | throw e 998 | } 999 | } else { 1000 | values(i) match { 1001 | case null => 1002 | val e = new NotFoundException 1003 | if (catcher.isDefinedAt(e)) { 1004 | catcher(e) 1005 | } else { 1006 | throw e 1007 | } 1008 | case value => 1009 | continue(value) 1010 | } 1011 | } 1012 | })) 1013 | commandRunner.flush() 1014 | } catch { 1015 | case e if catcher.isDefinedAt(e) => 1016 | catcher(e) 1017 | } 1018 | } 1019 | } 1020 | 1021 | /** Send an `delete` request to server. 1022 | * 1023 | * @param accessor the key being deleted. 1024 | * @param catcher the catcher to handle exceptions. 1025 | */ 1026 | @throws(classOf[NotFoundException]) 1027 | @throws(classOf[ServerErrorException]) 1028 | @throws(classOf[ProtocolException]) 1029 | final def delete[Value](accessor: StorageAccessor[Value])( 1030 | implicit catcher: Catcher[Unit]): Unit @suspendable = { 1031 | val commandRunner = getCommandRunnerForAccessor(accessor) 1032 | shift { (continue: Unit => Unit) => 1033 | logger.finer("Enqueue store command.") 1034 | try { 1035 | commandRunner.enqueue(new OtherCommand(continue) { 1036 | override final def apply( 1037 | socket: AsynchronousSocketChannel)( 1038 | implicit catcher: Catcher[Unit]): Unit @suspendable = { 1039 | AsciiProtocol.delete(socket, accessor) 1040 | } 1041 | }) 1042 | commandRunner.flush() 1043 | } catch { 1044 | case e if catcher.isDefinedAt(e) => 1045 | catcher(e) 1046 | } 1047 | } 1048 | } 1049 | } 1050 | 1051 | // vim: set ts=2 sw=2 et: 1052 | --------------------------------------------------------------------------------